Skip to content

Commit f9af7ae

Browse files
authored
[CLI] Move hostname, mgmt interface/vrf config to hostcfgd (#2)
This PR depends on sonic-net/sonic-utilities#2173 #### Why I did it To be able to configure the management interface and hostname standalone by changing database config at runtime. From the CLI perspective fo view, the following behavior is the same. But now you have two ways of configuring it: CLI, directly through the database. #### How I did it Moved configuration part of the interface and hostname to "hostcfgd". #### How to verify it * Built an image * Flash it to the switch * Run CLI commands ``` # Set IP address: verify address is set on the iface sudo config interface ip add eth0 10.210.25.127/22 10.210.24.1 ip address show eth0 # 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 # link/ether 98:03:9b:a2:be:80 brd ff:ff:ff:ff:ff:ff # inet 10.210.25.127/22 brd 10.210.27.255 scope global eth0 # valid_lft forever preferred_lft forever # inet6 fe80::9a03:9bff:fea2:be80/64 scope link # valid_lft forever preferred_lft forever # Remove IP address: verify you received address form DHCP sudo config interface ip remove eth0 10.210.25.127/22 ip address show eth0 # 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 # link/ether 98:03:9b:a2:be:80 brd ff:ff:ff:ff:ff:ff # inet 10.210.25.127/22 brd 10.210.27.255 scope global eth0 # valid_lft forever preferred_lft forever # inet6 fe80::9a03:9bff:fea2:be80/64 scope link # valid_lft forever preferred_lft forever # Enable/disable mgmt VRF ip address show mgmt # Device "mgmt" does not exist. sudo config vrf add mgmt ip address show mgmt # 72: mgmt: <NOARP,MASTER,UP,LOWER_UP> mtu 65575 qdisc noqueue state UP group default qlen 1000 # link/ether fa:9b:ad:7b:1e:83 brd ff:ff:ff:ff:ff:ff sudo config vrf del mgmt ip address show mgmt # Device "mgmt" does not exist. # Setting the hostname admin@r-anaconda-27:~$ sudo config hostname bla # Login / Logout admin@bla:~$ ``` #### Description for the changelog * Moved management interface configuration to hostcfgd. * Moved management VRF configuration to hostcfgd. * Moved hostname configuration to hostcfgd. #### Submodules PR's : | Repo | PR title | State | | ----------------- | ----------------- | ----------------- | | sonic-utilities | [[CLI] Move hostname, mgmt interface/vrf config to hostcfgd](sonic-net/sonic-utilities#2173) | ![GitHub issue/pull request detail](https://img.shields.io/github/pulls/detail/state/Azure/sonic-utilities/2173) |
1 parent 70ce6a3 commit f9af7ae

File tree

3 files changed

+283
-12
lines changed

3 files changed

+283
-12
lines changed

scripts/hostcfgd

+166-7
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import re
1212
import jinja2
1313
from sonic_py_common import device_info
1414
from swsscommon.swsscommon import ConfigDBConnector, DBConnector, Table
15+
from swsscommon import swsscommon
1516

1617
# FILE
1718
PAM_AUTH_CONF = "/etc/pam.d/common-auth-sonic"
@@ -1253,6 +1254,143 @@ class PamLimitsCfg(object):
12531254
"modify pam_limits config file failed with exception: {}"
12541255
.format(e))
12551256

1257+
class DeviceMetaCfg(object):
1258+
"""
1259+
DeviceMetaCfg Config Daemon
1260+
Handles changes in DEVICE_METADATA table.
1261+
1) Handle hostname change
1262+
"""
1263+
1264+
def __init__(self):
1265+
self.hostname = ''
1266+
1267+
def load(self, dev_meta={}):
1268+
# Get hostname initial
1269+
self.hostname = dev_meta.get('localhost', {}).get('hostname', '')
1270+
syslog.syslog(syslog.LOG_DEBUG, f'Initial hostname: {self.hostname}')
1271+
1272+
def hostname_update(self, data):
1273+
"""
1274+
Apply hostname handler.
1275+
1276+
Args:
1277+
data: Read table's key's data.
1278+
"""
1279+
syslog.syslog(syslog.LOG_DEBUG, 'DeviceMetaCfg: hostname update')
1280+
new_hostname = data.get('hostname')
1281+
1282+
# Restart hostname-config service when hostname was changed.
1283+
# Empty not allowed
1284+
if new_hostname and new_hostname != self.hostname:
1285+
syslog.syslog(syslog.LOG_INFO, 'DeviceMetaCfg: Set new hostname: {}'
1286+
.format(new_hostname))
1287+
self.hostname = new_hostname
1288+
try:
1289+
run_cmd('sudo service hostname-config restart', True, True)
1290+
except subprocess.CalledProcessError as e:
1291+
syslog.syslog(syslog.LOG_ERR, 'DeviceMetaCfg: Failed to set new'
1292+
' hostname: {}'.format(e))
1293+
return
1294+
1295+
run_cmd('sudo monit reload')
1296+
else:
1297+
msg = 'Hostname was not updated: '
1298+
msg += 'Already set up' if new_hostname else 'Empty not allowed'
1299+
syslog.syslog(syslog.LOG_ERR, msg)
1300+
1301+
1302+
class MgmtIfaceCfg(object):
1303+
"""
1304+
MgmtIfaceCfg Config Daemon
1305+
Handles changes in MGMT_INTERFACE, MGMT_VRF_CONFIG tables.
1306+
1) Handle change of interface ip
1307+
2) Handle change of management VRF state
1308+
"""
1309+
1310+
def __init__(self):
1311+
self.iface_config_data = {}
1312+
self.mgmt_vrf_enabled = ''
1313+
1314+
def load(self, mgmt_iface={}, mgmt_vrf={}):
1315+
# Get initial data
1316+
self.iface_config_data = mgmt_iface
1317+
self.mgmt_vrf_enabled = mgmt_vrf.get('mgmtVrfEnabled', '')
1318+
syslog.syslog(syslog.LOG_DEBUG,
1319+
f'Initial mgmt interface conf: {self.iface_config_data}')
1320+
syslog.syslog(syslog.LOG_DEBUG,
1321+
f'Initial mgmt VRF state: {self.mgmt_vrf_enabled}')
1322+
1323+
def update_mgmt_iface(self, iface, key, data):
1324+
"""Handle update management interface config
1325+
"""
1326+
syslog.syslog(syslog.LOG_DEBUG, 'MgmtIfaceCfg: mgmt iface update')
1327+
1328+
# Restart management interface service when config was changed
1329+
if data != self.iface_config_data.get(key):
1330+
cfg = {key: data}
1331+
syslog.syslog(syslog.LOG_INFO, f'MgmtIfaceCfg: Set new interface '
1332+
f'config {cfg} for {iface}')
1333+
try:
1334+
run_cmd('sudo systemctl restart interfaces-config', True, True)
1335+
run_cmd('sudo systemctl restart ntp-config', True, True)
1336+
except subprocess.CalledProcessError:
1337+
syslog.syslog(syslog.LOG_ERR, f'Failed to restart management '
1338+
'interface services')
1339+
return
1340+
1341+
self.iface_config_data[key] = data
1342+
1343+
def update_mgmt_vrf(self, data):
1344+
"""Handle update management VRF state
1345+
"""
1346+
syslog.syslog(syslog.LOG_DEBUG, 'MgmtIfaceCfg: mgmt vrf state update')
1347+
1348+
# Restart mgmt vrf services when mgmt vrf config was changed.
1349+
# Empty not allowed.
1350+
enabled = data.get('mgmtVrfEnabled', '')
1351+
if not enabled or enabled == self.mgmt_vrf_enabled:
1352+
return
1353+
1354+
syslog.syslog(syslog.LOG_INFO, f'Set mgmt vrf state {enabled}')
1355+
1356+
# Restart related vrfs services
1357+
try:
1358+
run_cmd('service ntp stop', True, True)
1359+
run_cmd('systemctl restart interfaces-config', True, True)
1360+
run_cmd('service ntp start', True, True)
1361+
except subprocess.CalledProcessError:
1362+
syslog.syslog(syslog.LOG_ERR, f'Failed to restart management vrf '
1363+
'services')
1364+
return
1365+
1366+
# Remove mgmt if route
1367+
if enabled == 'true':
1368+
"""
1369+
The regular expression for grep in below cmd is to match eth0 line
1370+
in /proc/net/route, sample file:
1371+
$ cat /proc/net/route
1372+
Iface Destination Gateway Flags RefCnt Use
1373+
eth0 00000000 01803B0A 0003 0 0
1374+
#################### Line break here ####################
1375+
Metric Mask MTU Window IRTT
1376+
202 00000000 0 0 0
1377+
"""
1378+
try:
1379+
run_cmd(r"""cat /proc/net/route | grep -E \"eth0\s+"""
1380+
r"""00000000\s+[0-9A-Z]+\s+[0-9]+\s+[0-9]+\s+[0-9]+"""
1381+
r"""\s+202\" | wc -l""",
1382+
True, True)
1383+
except subprocess.CalledProcessError:
1384+
syslog.syslog(syslog.LOG_ERR, 'MgmtIfaceCfg: Could not delete '
1385+
'eth0 route')
1386+
return
1387+
1388+
run_cmd("ip -4 route del default dev eth0 metric 202", False)
1389+
1390+
# Update cache
1391+
self.mgmt_vrf_enabled = enabled
1392+
1393+
12561394
class HostConfigDaemon:
12571395
def __init__(self):
12581396
# Just a sanity check to verify if the CONFIG_DB has been initialized
@@ -1284,7 +1422,6 @@ class HostConfigDaemon:
12841422
self.is_multi_npu = device_info.is_multi_npu()
12851423

12861424
# Initialize AAACfg
1287-
self.hostname_cache=""
12881425
self.aaacfg = AaaCfg()
12891426

12901427
# Initialize PasswHardening
@@ -1294,6 +1431,12 @@ class HostConfigDaemon:
12941431
self.pamLimitsCfg = PamLimitsCfg(self.config_db)
12951432
self.pamLimitsCfg.update_config_file()
12961433

1434+
# Initialize DeviceMetaCfg
1435+
self.devmetacfg = DeviceMetaCfg()
1436+
1437+
# Initialize MgmtIfaceCfg
1438+
self.mgmtifacecfg = MgmtIfaceCfg()
1439+
12971440
def load(self, init_data):
12981441
features = init_data['FEATURE']
12991442
aaa = init_data['AAA']
@@ -1306,21 +1449,21 @@ class HostConfigDaemon:
13061449
ntp_global = init_data['NTP']
13071450
kdump = init_data['KDUMP']
13081451
passwh = init_data['PASSW_HARDENING']
1452+
dev_meta = init_data.get(swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, {})
1453+
mgmt_ifc = init_data.get(swsscommon.CFG_MGMT_INTERFACE_TABLE_NAME, {})
1454+
mgmt_vrf = init_data.get(swsscommon.CFG_MGMT_VRF_CONFIG_TABLE_NAME, {})
13091455

13101456
self.feature_handler.sync_state_field(features)
13111457
self.aaacfg.load(aaa, tacacs_global, tacacs_server, radius_global, radius_server)
13121458
self.iptables.load(lpbk_table)
13131459
self.ntpcfg.load(ntp_global, ntp_server)
13141460
self.kdumpCfg.load(kdump)
13151461
self.passwcfg.load(passwh)
1316-
1317-
dev_meta = self.config_db.get_table('DEVICE_METADATA')
1318-
if 'localhost' in dev_meta:
1319-
if 'hostname' in dev_meta['localhost']:
1320-
self.hostname_cache = dev_meta['localhost']['hostname']
1462+
self.devmetacfg.load(dev_meta)
1463+
self.mgmtifacecfg.load(mgmt_ifc, mgmt_vrf)
13211464

13221465
# Update AAA with the hostname
1323-
self.aaacfg.hostname_update(self.hostname_cache)
1466+
self.aaacfg.hostname_update(self.devmetacfg.hostname)
13241467

13251468
def __get_intf_name(self, key):
13261469
if isinstance(key, tuple) and key:
@@ -1370,6 +1513,10 @@ class HostConfigDaemon:
13701513
mgmt_intf_name = self.__get_intf_name(key)
13711514
self.aaacfg.handle_radius_source_intf_ip_chg(mgmt_intf_name)
13721515
self.aaacfg.handle_radius_nas_ip_chg(mgmt_intf_name)
1516+
self.mgmtifacecfg.update_mgmt_iface(mgmt_intf_name, key, data)
1517+
1518+
def mgmt_vrf_handler(self, key, op, data):
1519+
self.mgmtifacecfg.update_mgmt_vrf(data)
13731520

13741521
def lpbk_handler(self, key, op, data):
13751522
key = ConfigDBConnector.deserialize_key(key)
@@ -1409,6 +1556,10 @@ class HostConfigDaemon:
14091556
syslog.syslog(syslog.LOG_INFO, 'Kdump handler...')
14101557
self.kdumpCfg.kdump_update(key, data)
14111558

1559+
def device_metadata_handler(self, key, op, data):
1560+
syslog.syslog(syslog.LOG_INFO, 'DeviceMeta handler...')
1561+
self.devmetacfg.hostname_update(data)
1562+
14121563
def wait_till_system_init_done(self):
14131564
# No need to print the output in the log file so using the "--quiet"
14141565
# flag
@@ -1448,6 +1599,14 @@ class HostConfigDaemon:
14481599
self.config_db.subscribe('PORTCHANNEL_INTERFACE', make_callback(self.portchannel_intf_handler))
14491600
self.config_db.subscribe('INTERFACE', make_callback(self.phy_intf_handler))
14501601

1602+
# Handle DEVICE_MEATADATA changes
1603+
self.config_db.subscribe(swsscommon.CFG_DEVICE_METADATA_TABLE_NAME,
1604+
make_callback(self.device_metadata_handler))
1605+
1606+
# Handle MGMT_VRF_CONFIG changes
1607+
self.config_db.subscribe(swsscommon.CFG_MGMT_VRF_CONFIG_TABLE_NAME,
1608+
make_callback(self.mgmt_vrf_handler))
1609+
14511610
syslog.syslog(syslog.LOG_INFO,
14521611
"Waiting for systemctl to finish initialization")
14531612
self.wait_till_system_init_done()

tests/hostcfgd/hostcfgd_test.py

+75
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from sonic_py_common.general import load_module_from_source
88
from unittest import TestCase, mock
99

10+
from .test_vectors import HOSTCFG_DAEMON_INIT_CFG_DB
1011
from .test_vectors import HOSTCFGD_TEST_VECTOR, HOSTCFG_DAEMON_CFG_DB
1112
from tests.common.mock_configdb import MockConfigDb, MockDBConnector
1213

@@ -357,3 +358,77 @@ def test_kdump_event(self):
357358
call('sonic-kdump-config --num_dumps 3', shell=True),
358359
call('sonic-kdump-config --memory 0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M', shell=True)]
359360
mocked_subprocess.check_call.assert_has_calls(expected, any_order=True)
361+
362+
def test_devicemeta_event(self):
363+
"""
364+
Test handling DEVICE_METADATA events.
365+
1) Hostname reload
366+
"""
367+
MockConfigDb.set_config_db(HOSTCFG_DAEMON_CFG_DB)
368+
MockConfigDb.event_queue = [(swsscommon.CFG_DEVICE_METADATA_TABLE_NAME,
369+
'localhost')]
370+
daemon = hostcfgd.HostConfigDaemon()
371+
daemon.aaacfg = mock.MagicMock()
372+
daemon.iptables = mock.MagicMock()
373+
daemon.passwcfg = mock.MagicMock()
374+
daemon.load(HOSTCFG_DAEMON_INIT_CFG_DB)
375+
daemon.register_callbacks()
376+
with mock.patch('hostcfgd.subprocess') as mocked_subprocess:
377+
popen_mock = mock.Mock()
378+
attrs = {'communicate.return_value': ('output', 'error')}
379+
popen_mock.configure_mock(**attrs)
380+
mocked_subprocess.Popen.return_value = popen_mock
381+
382+
try:
383+
daemon.start()
384+
except TimeoutError:
385+
pass
386+
387+
expected = [
388+
call('sudo service hostname-config restart', shell=True),
389+
call('sudo monit reload', shell=True)
390+
]
391+
mocked_subprocess.check_call.assert_has_calls(expected,
392+
any_order=True)
393+
394+
def test_mgmtiface_event(self):
395+
"""
396+
Test handling mgmt events.
397+
1) Management interface setup
398+
2) Management vrf setup
399+
"""
400+
MockConfigDb.set_config_db(HOSTCFG_DAEMON_CFG_DB)
401+
MockConfigDb.event_queue = [
402+
(swsscommon.CFG_MGMT_INTERFACE_TABLE_NAME, 'eth0|1.2.3.4/24'),
403+
(swsscommon.CFG_MGMT_VRF_CONFIG_TABLE_NAME, 'vrf_global')
404+
]
405+
daemon = hostcfgd.HostConfigDaemon()
406+
daemon.register_callbacks()
407+
daemon.aaacfg = mock.MagicMock()
408+
daemon.iptables = mock.MagicMock()
409+
daemon.passwcfg = mock.MagicMock()
410+
daemon.load(HOSTCFG_DAEMON_INIT_CFG_DB)
411+
with mock.patch('hostcfgd.subprocess') as mocked_subprocess:
412+
popen_mock = mock.Mock()
413+
attrs = {'communicate.return_value': ('output', 'error')}
414+
popen_mock.configure_mock(**attrs)
415+
mocked_subprocess.Popen.return_value = popen_mock
416+
417+
try:
418+
daemon.start()
419+
except TimeoutError:
420+
pass
421+
422+
expected = [
423+
call('sudo systemctl restart interfaces-config', shell=True),
424+
call('sudo systemctl restart ntp-config', shell=True),
425+
call('service ntp stop', shell=True),
426+
call('systemctl restart interfaces-config', shell=True),
427+
call('service ntp start', shell=True),
428+
call('cat /proc/net/route | grep -E \\"eth0\\s+00000000'
429+
'\\s+[0-9A-Z]+\\s+[0-9]+\\s+[0-9]+\\s+[0-9]+\\s+202\\" | '
430+
'wc -l', shell=True),
431+
call('ip -4 route del default dev eth0 metric 202', shell=True)
432+
]
433+
mocked_subprocess.check_call.assert_has_calls(expected,
434+
any_order=True)

0 commit comments

Comments
 (0)