diff --git a/src/sonic-host-services/scripts/hostcfgd b/src/sonic-host-services/scripts/hostcfgd index 7f8f4be0b2e..127c2e20422 100755 --- a/src/sonic-host-services/scripts/hostcfgd +++ b/src/sonic-host-services/scripts/hostcfgd @@ -12,6 +12,7 @@ import signal import jinja2 from sonic_py_common import device_info from swsscommon.swsscommon import ConfigDBConnector, DBConnector, Table +from swsscommon import swsscommon # FILE PAM_AUTH_CONF = "/etc/pam.d/common-auth-sonic" @@ -1046,6 +1047,125 @@ class PamLimitsCfg(object): "modify pam_limits config file failed with exception: {}" .format(e)) +class DeviceMetaCfg(object): + """ + DeviceMetaCfg Config Daemon + Handles changes in DEVICE_METADATA table. + 1) Handle hostname change + """ + + def __init__(self): + self.hostname = '' + + def hostname_update(self, data): + """ + Apply hostname handler. + + Args: + data: Read table's key's data. + """ + syslog.syslog(syslog.LOG_DEBUG, 'DeviceMetaCfg: hostname update') + new_hostname = data.get('hostname') + + # Restart hostname-config service when hostname was changed. + # Empty not allowed + if new_hostname and new_hostname != self.hostname: + syslog.syslog(syslog.LOG_INFO, 'DeviceMetaCfg: Set new hostname: {}' + .format(new_hostname)) + self.hostname = new_hostname + try: + run_cmd('sudo service hostname-config restart', True, True) + except Exception as e: + syslog.syslog(syslog.LOG_ERR, 'DeviceMetaCfg: Failed to set new' + ' hostname: {}'.format(e)) + return + + run_cmd('sudo monit reload') + + +class MgmtIfaceCfg(object): + """ + MgmtIfaceCfg Config Daemon + Handles changes in MGMT_INTERFACE, MGMT_VRF_CONFIG tables. + 1) Handle change of interface ip + 2) Handle change of management VRF state + """ + + def __init__(self): + self.iface_config_data = {} + self.mgmt_vrf_enabled = '' + + def update_mgmt_iface(self, iface, key, data): + """Handle update management interface config + """ + syslog.syslog(syslog.LOG_DEBUG, 'MgmtIfaceCfg: mgmt iface update') + + # Restart management interface service when config was changed + if data != self.iface_config_data.get(key): + cfg = {key: data} + syslog.syslog(syslog.LOG_INFO, f'MgmtIfaceCfg: Set new interface ' + f'config {cfg} for {iface}') + try: + run_cmd('sudo systemctl restart interfaces-config', True, True) + run_cmd('sudo systemctl restart ntp-config', True, True) + except Exception: + syslog.syslog(syslog.LOG_ERR, f'Failed to restart management ' + 'interface services') + return + + self.iface_config_data[key] = data + + def update_mgmt_vrf(self, data): + """Handle update management VRF state + """ + syslog.syslog(syslog.LOG_DEBUG, 'MgmtIfaceCfg: mgmt vrf state update') + + # Restart mgmt vrf services when mgmt vrf config was changed. + # Empty not allowed. + enabled = data.get('mgmtVrfEnabled', '') + if not enabled or enabled == self.mgmt_vrf_enabled: + return + + syslog.syslog(syslog.LOG_INFO, f'Set mgmt vrf state {enabled}') + + # Restart related vrfs services + try: + run_cmd('service ntp stop', True, True) + run_cmd('systemctl restart interfaces-config', True, True) + run_cmd('service ntp start', True, True) + except Exception: + syslog.syslog(syslog.LOG_ERR, f'Failed to restart management vrf ' + 'services') + return + + # Remove mgmt if route + if enabled == 'true': + """ + The regular expression for grep in below cmd is to match eth0 line + in /proc/net/route, sample file: + $ cat /proc/net/route + Iface Destination Gateway Flags RefCnt Use + eth0 00000000 01803B0A 0003 0 0 + #################### Line break here #################### + Metric Mask MTU Window IRTT + 202 00000000 0 0 0 + """ + try: + run_cmd(r"""cat /proc/net/route | grep -E \"eth0\s+""" + r"""00000000\s+[0-9A-Z]+\s+[0-9]+\s+[0-9]+\s+[0-9]+""" + r"""\s+202\" | wc -l""", + True, True) + except Exception: + syslog.syslog(syslog.LOG_ERR, 'MgmtIfaceCfg: Could not delete ' + 'eth0 route') + return + + run_cmd("ip -4 route del default dev eth0 metric 202", False) + + # Update cache + self.mgmt_vrf_enabled = enabled + + class HostConfigDaemon: def __init__(self): # Just a sanity check to verify if the CONFIG_DB has been initialized @@ -1084,6 +1204,12 @@ class HostConfigDaemon: self.pamLimitsCfg = PamLimitsCfg(self.config_db) self.pamLimitsCfg.update_config_file() + # Initialize DeviceMetaCfg + self.devmetacfg = DeviceMetaCfg() + + # Initialize MgmtIfaceCfg + self.mgmtifacecfg = MgmtIfaceCfg() + def load(self, init_data): features = init_data['FEATURE'] aaa = init_data['AAA'] @@ -1102,10 +1228,10 @@ class HostConfigDaemon: self.ntpcfg.load(ntp_global, ntp_server) self.kdumpCfg.load(kdump) - dev_meta = self.config_db.get_table('DEVICE_METADATA') - if 'localhost' in dev_meta: - if 'hostname' in dev_meta['localhost']: - self.hostname_cache = dev_meta['localhost']['hostname'] + # Get hostname + self.hostname_cache = self.config_db.get_table( + swsscommon.CFG_DEVICE_METADATA_TABLE_NAME + ).get('localhost', {}).get('hostname', '') # Update AAA with the hostname self.aaacfg.hostname_update(self.hostname_cache) @@ -1154,6 +1280,10 @@ class HostConfigDaemon: mgmt_intf_name = self.__get_intf_name(key) self.aaacfg.handle_radius_source_intf_ip_chg(mgmt_intf_name) self.aaacfg.handle_radius_nas_ip_chg(mgmt_intf_name) + self.mgmtifacecfg.update_mgmt_iface(mgmt_intf_name, key, data) + + def mgmt_vrf_handler(self, key, op, data): + self.mgmtifacecfg.update_mgmt_vrf(data) def lpbk_handler(self, key, op, data): key = ConfigDBConnector.deserialize_key(key) @@ -1193,6 +1323,10 @@ class HostConfigDaemon: syslog.syslog(syslog.LOG_INFO, 'Kdump handler...') self.kdumpCfg.kdump_update(key, data) + def device_metadata_handler(self, key, op, data): + syslog.syslog(syslog.LOG_INFO, 'DeviceMeta handler...') + self.devmetacfg.hostname_update(data) + def wait_till_system_init_done(self): # No need to print the output in the log file so using the "--quiet" # flag @@ -1231,6 +1365,14 @@ class HostConfigDaemon: self.config_db.subscribe('PORTCHANNEL_INTERFACE', make_callback(self.portchannel_intf_handler)) self.config_db.subscribe('INTERFACE', make_callback(self.phy_intf_handler)) + # Handle DEVICE_MEATADATA changes + self.config_db.subscribe(swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, + make_callback(self.device_metadata_handler)) + + # Handle MGMT_VRF_CONFIG changes + self.config_db.subscribe(swsscommon.CFG_MGMT_VRF_CONFIG_TABLE_NAME, + make_callback(self.mgmt_vrf_handler)) + syslog.syslog(syslog.LOG_INFO, "Waiting for systemctl to finish initialization") self.wait_till_system_init_done() diff --git a/src/sonic-host-services/tests/hostcfgd/hostcfgd_test.py b/src/sonic-host-services/tests/hostcfgd/hostcfgd_test.py index 28d4f6f8a72..f4f45a3a648 100644 --- a/src/sonic-host-services/tests/hostcfgd/hostcfgd_test.py +++ b/src/sonic-host-services/tests/hostcfgd/hostcfgd_test.py @@ -307,3 +307,31 @@ def test_kdump_event(self): call('sonic-kdump-config --num_dumps 3', shell=True), call('sonic-kdump-config --memory 0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M', shell=True)] mocked_subprocess.check_call.assert_has_calls(expected, any_order=True) + + def test_devicemeta_event(self): + """ + Test handling DEVICE_METADATA events. + 1) Hostname reload + """ + MockConfigDb.set_config_db(HOSTCFG_DAEMON_CFG_DB) + MockConfigDb.event_queue = [(swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, + 'localhost')] + daemon = hostcfgd.HostConfigDaemon() + 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: + pass + + expected = [ + call('sudo service hostname-config restart', shell=True), + call('sudo monit reload', shell=True) + ] + mocked_subprocess.check_call.assert_has_calls(expected, + any_order=True) diff --git a/src/sonic-host-services/tests/hostcfgd/test_vectors.py b/src/sonic-host-services/tests/hostcfgd/test_vectors.py index 28ffe89d841..1055b6fb2c7 100644 --- a/src/sonic-host-services/tests/hostcfgd/test_vectors.py +++ b/src/sonic-host-services/tests/hostcfgd/test_vectors.py @@ -548,6 +548,7 @@ "localhost": { "subtype": "DualToR", "type": "ToRRouter", + "hostname": "SomeNewHostname" } } } diff --git a/src/sonic-utilities b/src/sonic-utilities index 288c2d8d932..f6d735c7cab 160000 --- a/src/sonic-utilities +++ b/src/sonic-utilities @@ -1 +1 @@ -Subproject commit 288c2d8d932899c71c34d360983cee640a9694fd +Subproject commit f6d735c7cabf687d1d8b5eb42af37b12f4cdae21