[hostcfgd] Move hostcfgd back to ConfigDBConnector for subscribing to updates (#10168)

#### Why I did it

As of https://github.com/Azure/sonic-swss-common/pull/587 the blackout issue in ConfigDBConnector has been resolved. 

In the past hostcfgd was refactored to use SubscriberStateTable instead of ConfigDBConnector for subscribing to CONFIG_DB updates due to a "blackout" period between hostcfgd pulling the table data down and running the initialization and actually calling `listen()` on ConfigDBConnector which starts the update handler. 

However SusbscriberStateTable creates many file descriptors against the redis DB which is inefficient compared to ConfigDBConnector which only opens a single file descriptor. 

With the new fix to ConfigDBConnector I refactored hostcfgd to take advantage of these updates.

#### How I did it

Replaced SubscriberStateTable with ConfigDBConnector

#### How to verify it

The functionality of hostcfgd can be verified by booting the switch and verifying that NTP is properly configured.

To check the blackout period you can add a delay in the hostcfgd `load()` function and also add a print statement before and after the load so you know when it occurs. Then restart hostcfgd and wait for the load to start, then during the load push a partial change to the FEATURE table and verify that the change is picked up and the feature is enabled after the load period finishes. 

#### Description for the changelog
[hostcfgd] Move hostcfgd back to ConfigDBConnector for subscribing to updates
This commit is contained in:
Alexander Allen 2022-04-07 17:56:52 -04:00 committed by GitHub
parent f311947cfa
commit 47db2b2993
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 106 additions and 182 deletions

View File

@ -11,8 +11,7 @@ import signal
import jinja2 import jinja2
from sonic_py_common import device_info from sonic_py_common import device_info
from swsscommon.swsscommon import SubscriberStateTable, DBConnector, Select from swsscommon.swsscommon import ConfigDBConnector, DBConnector, Table
from swsscommon.swsscommon import ConfigDBConnector, TableConsumable, Table
# FILE # FILE
PAM_AUTH_CONF = "/etc/pam.d/common-auth-sonic" PAM_AUTH_CONF = "/etc/pam.d/common-auth-sonic"
@ -207,20 +206,22 @@ class FeatureHandler(object):
else: else:
self.resync_feature_state(self._cached_config[feature_name]) self.resync_feature_state(self._cached_config[feature_name])
def sync_state_field(self): def sync_state_field(self, feature_table):
""" """
Summary: Summary:
Updates the state field in the FEATURE|* tables as the state field Updates the state field in the FEATURE|* tables as the state field
might have to be rendered based on DEVICE_METADATA table might have to be rendered based on DEVICE_METADATA table
""" """
feature_table = self._config_db.get_table('FEATURE')
for feature_name in feature_table.keys(): for feature_name in feature_table.keys():
if not feature_name: if not feature_name:
syslog.syslog(syslog.LOG_WARNING, "Feature is None") syslog.syslog(syslog.LOG_WARNING, "Feature is None")
continue continue
feature = Feature(feature_name, feature_table[feature_name], self._device_config) feature = Feature(feature_name, feature_table[feature_name], self._device_config)
if not feature.compare_state(feature_name, feature_table.get(feature_name, {})):
self._cached_config.setdefault(feature_name, feature)
self.update_feature_auto_restart(feature, feature_name)
self.update_feature_state(feature)
self.resync_feature_state(feature) self.resync_feature_state(feature)
def update_feature_state(self, feature): def update_feature_state(self, feature):
@ -406,6 +407,10 @@ class Iptables(object):
''' '''
return (isinstance(key, tuple)) return (isinstance(key, tuple))
def load(self, lpbk_table):
for row in lpbk_table:
self.iptables_handler(row, lpbk_table[row])
def command(self, chain, ip, ver, op): def command(self, chain, ip, ver, op):
cmd = 'iptables' if ver == '4' else 'ip6tables' cmd = 'iptables' if ver == '4' else 'ip6tables'
cmd += ' -t mangle --{} {} -p tcp --tcp-flags SYN SYN'.format(op, chain) cmd += ' -t mangle --{} {} -p tcp --tcp-flags SYN SYN'.format(op, chain)
@ -890,14 +895,12 @@ class KdumpCfg(object):
memory = self.kdump_defaults["memory"] memory = self.kdump_defaults["memory"]
if data.get("memory") is not None: if data.get("memory") is not None:
memory = data.get("memory") memory = data.get("memory")
if data.get("memory") is not None:
run_cmd("sonic-kdump-config --memory " + memory) run_cmd("sonic-kdump-config --memory " + memory)
# Num dumps # Num dumps
num_dumps = self.kdump_defaults["num_dumps"] num_dumps = self.kdump_defaults["num_dumps"]
if data.get("num_dumps") is not None: if data.get("num_dumps") is not None:
num_dumps = data.get("num_dumps") num_dumps = data.get("num_dumps")
if data.get("num_dumps") is not None:
run_cmd("sonic-kdump-config --num_dumps " + num_dumps) run_cmd("sonic-kdump-config --num_dumps " + num_dumps)
class NtpCfg(object): class NtpCfg(object):
@ -912,6 +915,15 @@ class NtpCfg(object):
self.ntp_global = {} self.ntp_global = {}
self.ntp_servers = set() self.ntp_servers = set()
def load(self, ntp_global_conf, ntp_server_conf):
syslog.syslog(syslog.LOG_INFO, "NtpCfg load ...")
for row in ntp_global_conf:
self.ntp_global_update(row, ntp_global_conf[row], is_load=True)
# Force reload on init
self.ntp_server_update(0, None, is_load=True)
def handle_ntp_source_intf_chg(self, intf_name): def handle_ntp_source_intf_chg(self, intf_name):
# if no ntp server configured, do nothing # if no ntp server configured, do nothing
if not self.ntp_servers: if not self.ntp_servers:
@ -925,7 +937,7 @@ class NtpCfg(object):
cmd = 'systemctl restart ntp-config' cmd = 'systemctl restart ntp-config'
run_cmd(cmd) run_cmd(cmd)
def ntp_global_update(self, key, data): def ntp_global_update(self, key, data, is_load=False):
syslog.syslog(syslog.LOG_INFO, 'NTP GLOBAL Update') syslog.syslog(syslog.LOG_INFO, 'NTP GLOBAL Update')
orig_src = self.ntp_global.get('src_intf', '') orig_src = self.ntp_global.get('src_intf', '')
orig_src_set = set(orig_src.split(";")) orig_src_set = set(orig_src.split(";"))
@ -938,6 +950,9 @@ class NtpCfg(object):
# Update the Local Cache # Update the Local Cache
self.ntp_global = data self.ntp_global = data
# If initial load don't restart daemon
if is_load: return
# check if ntp server configured, if not, do nothing # check if ntp server configured, if not, do nothing
if not self.ntp_servers: if not self.ntp_servers:
syslog.syslog(syslog.LOG_INFO, "No ntp server when global config change, do nothing") syslog.syslog(syslog.LOG_INFO, "No ntp server when global config change, do nothing")
@ -954,16 +969,19 @@ class NtpCfg(object):
cmd = 'service ntp restart' cmd = 'service ntp restart'
run_cmd(cmd) run_cmd(cmd)
def ntp_server_update(self, key, op): def ntp_server_update(self, key, op, is_load=False):
syslog.syslog(syslog.LOG_INFO, 'ntp server update key {}'.format(key)) syslog.syslog(syslog.LOG_INFO, 'ntp server update key {}'.format(key))
restart_config = False restart_config = False
if not is_load:
if op == "SET" and key not in self.ntp_servers: if op == "SET" and key not in self.ntp_servers:
restart_config = True restart_config = True
self.ntp_servers.add(key) self.ntp_servers.add(key)
elif op == "DEL" and key in self.ntp_servers: elif op == "DEL" and key in self.ntp_servers:
restart_config = True restart_config = True
self.ntp_servers.remove(key) self.ntp_servers.remove(key)
else:
restart_config = True
if restart_config: if restart_config:
cmd = 'systemctl restart ntp-config' cmd = 'systemctl restart ntp-config'
@ -1034,31 +1052,24 @@ class HostConfigDaemon:
# before moving forward # before moving forward
self.config_db = ConfigDBConnector() self.config_db = ConfigDBConnector()
self.config_db.connect(wait_for_init=True, retry_on=True) self.config_db.connect(wait_for_init=True, retry_on=True)
self.dbconn = DBConnector(CFG_DB, 0)
self.state_db_conn = DBConnector(STATE_DB, 0)
self.selector = Select()
syslog.syslog(syslog.LOG_INFO, 'ConfigDB connect success') syslog.syslog(syslog.LOG_INFO, 'ConfigDB connect success')
self.select = Select()
self.callbacks = dict()
self.subscriber_map = dict()
feature_state_table = Table(self.state_db_conn, 'FEATURE')
# Load DEVICE metadata configurations # Load DEVICE metadata configurations
self.device_config = {} self.device_config = {}
self.device_config['DEVICE_METADATA'] = self.config_db.get_table('DEVICE_METADATA') self.device_config['DEVICE_METADATA'] = self.config_db.get_table('DEVICE_METADATA')
# Load feature state table
self.state_db_conn = DBConnector(STATE_DB, 0)
feature_state_table = Table(self.state_db_conn, 'FEATURE')
# Initialize KDump Config and set the config to default if nothing is provided # Initialize KDump Config and set the config to default if nothing is provided
self.kdumpCfg = KdumpCfg(self.config_db) self.kdumpCfg = KdumpCfg(self.config_db)
self.kdumpCfg.load(self.config_db.get_table('KDUMP'))
# Initialize IpTables # Initialize IpTables
self.iptables = Iptables() self.iptables = Iptables()
# Intialize Feature Handler # Intialize Feature Handler
self.feature_handler = FeatureHandler(self.config_db, feature_state_table, self.device_config) self.feature_handler = FeatureHandler(self.config_db, feature_state_table, self.device_config)
self.feature_handler.sync_state_field()
# Initialize Ntp Config Handler # Initialize Ntp Config Handler
self.ntpcfg = NtpCfg() self.ntpcfg = NtpCfg()
@ -1073,21 +1084,28 @@ class HostConfigDaemon:
self.pamLimitsCfg = PamLimitsCfg(self.config_db) self.pamLimitsCfg = PamLimitsCfg(self.config_db)
self.pamLimitsCfg.update_config_file() self.pamLimitsCfg.update_config_file()
def load(self): def load(self, init_data):
aaa = self.config_db.get_table('AAA') features = init_data['FEATURE']
tacacs_global = self.config_db.get_table('TACPLUS') aaa = init_data['AAA']
tacacs_server = self.config_db.get_table('TACPLUS_SERVER') tacacs_global = init_data['TACPLUS']
radius_global = self.config_db.get_table('RADIUS') tacacs_server = init_data['TACPLUS_SERVER']
radius_server = self.config_db.get_table('RADIUS_SERVER') radius_global = init_data['RADIUS']
self.aaacfg.load(aaa, tacacs_global, tacacs_server, radius_global, radius_server) radius_server = init_data['RADIUS_SERVER']
lpbk_table = init_data['LOOPBACK_INTERFACE']
ntp_server = init_data['NTP_SERVER']
ntp_global = init_data['NTP']
kdump = init_data['KDUMP']
self.feature_handler.sync_state_field(features)
self.aaacfg.load(aaa, tacacs_global, tacacs_server, radius_global, radius_server)
self.iptables.load(lpbk_table)
self.ntpcfg.load(ntp_global, ntp_server)
self.kdumpCfg.load(kdump)
try:
dev_meta = self.config_db.get_table('DEVICE_METADATA') dev_meta = self.config_db.get_table('DEVICE_METADATA')
if 'localhost' in dev_meta: if 'localhost' in dev_meta:
if 'hostname' in dev_meta['localhost']: if 'hostname' in dev_meta['localhost']:
self.hostname_cache = dev_meta['localhost']['hostname'] self.hostname_cache = dev_meta['localhost']['hostname']
except Exception as e:
pass
# Update AAA with the hostname # Update AAA with the hostname
self.aaacfg.hostname_update(self.hostname_cache) self.aaacfg.hostname_update(self.hostname_cache)
@ -1181,39 +1199,37 @@ class HostConfigDaemon:
systemctl_cmd = "sudo systemctl is-system-running --wait --quiet" systemctl_cmd = "sudo systemctl is-system-running --wait --quiet"
subprocess.call(systemctl_cmd, shell=True) subprocess.call(systemctl_cmd, shell=True)
def subscribe(self, table, callback, pri):
try:
if table not in self.callbacks:
self.callbacks[table] = []
subscriber = SubscriberStateTable(self.dbconn, table, TableConsumable.DEFAULT_POP_BATCH_SIZE, pri)
self.selector.addSelectable(subscriber) # Add to the Selector
self.subscriber_map[subscriber.getFd()] = (subscriber, table) # Maintain a mapping b/w subscriber & fd
self.callbacks[table].append(callback)
except Exception as err:
syslog.syslog(syslog.LOG_ERR, "Subscribe to table {} failed with error {}".format(table, err))
def register_callbacks(self): def register_callbacks(self):
self.subscribe('KDUMP', lambda table, key, op, data: self.kdump_handler(key, op, data), HOSTCFGD_MAX_PRI)
def make_callback(func):
def callback(table, key, data):
if data is None:
op = "DEL"
else:
op = "SET"
return func(key, op, data)
return callback
self.config_db.subscribe('KDUMP', make_callback(self.kdump_handler))
# Handle FEATURE updates before other tables # Handle FEATURE updates before other tables
self.subscribe('FEATURE', lambda table, key, op, data: self.feature_handler.handle(key, op, data), HOSTCFGD_MAX_PRI-1) self.config_db.subscribe('FEATURE', make_callback(self.feature_handler.handle))
# Handle AAA, TACACS and RADIUS related tables # Handle AAA, TACACS and RADIUS related tables
self.subscribe('AAA', lambda table, key, op, data: self.aaa_handler(key, op, data), HOSTCFGD_MAX_PRI-2) self.config_db.subscribe('AAA', make_callback(self.aaa_handler))
self.subscribe('TACPLUS', lambda table, key, op, data: self.tacacs_global_handler(key, op, data), HOSTCFGD_MAX_PRI-2) self.config_db.subscribe('TACPLUS', make_callback(self.tacacs_global_handler))
self.subscribe('TACPLUS_SERVER', lambda table, key, op, data: self.tacacs_server_handler(key, op, data), HOSTCFGD_MAX_PRI-2) self.config_db.subscribe('TACPLUS_SERVER', make_callback(self.tacacs_server_handler))
self.subscribe('RADIUS', lambda table, key, op, data: self.radius_global_handler(key, op, data), HOSTCFGD_MAX_PRI-2) self.config_db.subscribe('RADIUS', make_callback(self.radius_global_handler))
self.subscribe('RADIUS_SERVER', lambda table, key, op, data: self.radius_server_handler(key, op, data), HOSTCFGD_MAX_PRI-2) self.config_db.subscribe('RADIUS_SERVER', make_callback(self.radius_server_handler))
# Handle IPTables configuration # Handle IPTables configuration
self.subscribe('LOOPBACK_INTERFACE', lambda table, key, op, data: self.lpbk_handler(key, op, data), HOSTCFGD_MAX_PRI-3) self.config_db.subscribe('LOOPBACK_INTERFACE', make_callback(self.lpbk_handler))
# Handle NTP & NTP_SERVER updates # Handle NTP & NTP_SERVER updates
self.subscribe('NTP', lambda table, key, op, data: self.ntp_global_handler(key, op, data), HOSTCFGD_MAX_PRI-4) self.config_db.subscribe('NTP', make_callback(self.ntp_global_handler))
self.subscribe('NTP_SERVER', lambda table, key, op, data: self.ntp_server_handler(key, op, data), HOSTCFGD_MAX_PRI-4) self.config_db.subscribe('NTP_SERVER', make_callback(self.ntp_server_handler))
# Handle updates to src intf changes in radius # Handle updates to src intf changes in radius
self.subscribe('MGMT_INTERFACE', lambda table, key, op, data: self.mgmt_intf_handler(key, op, data), HOSTCFGD_MAX_PRI-5) self.config_db.subscribe('MGMT_INTERFACE', make_callback(self.mgmt_intf_handler))
self.subscribe('VLAN_INTERFACE', lambda table, key, op, data: self.vlan_intf_handler(key, op, data), HOSTCFGD_MAX_PRI-5) self.config_db.subscribe('VLAN_INTERFACE', make_callback(self.vlan_intf_handler))
self.subscribe('VLAN_SUB_INTERFACE', lambda table, key, op, data: self.vlan_sub_intf_handler(key, op, data), HOSTCFGD_MAX_PRI-5) self.config_db.subscribe('VLAN_SUB_INTERFACE', make_callback(self.vlan_sub_intf_handler))
self.subscribe('PORTCHANNEL_INTERFACE', lambda table, key, op, data: self.portchannel_intf_handler(key, op, data), HOSTCFGD_MAX_PRI-5) self.config_db.subscribe('PORTCHANNEL_INTERFACE', make_callback(self.portchannel_intf_handler))
self.subscribe('INTERFACE', lambda table, key, op, data: self.phy_intf_handler(key, op, data), HOSTCFGD_MAX_PRI-5) self.config_db.subscribe('INTERFACE', make_callback(self.phy_intf_handler))
syslog.syslog(syslog.LOG_INFO, syslog.syslog(syslog.LOG_INFO,
"Waiting for systemctl to finish initialization") "Waiting for systemctl to finish initialization")
@ -1222,27 +1238,7 @@ class HostConfigDaemon:
"systemctl has finished initialization -- proceeding ...") "systemctl has finished initialization -- proceeding ...")
def start(self): def start(self):
while True: self.config_db.listen(init_data_handler=self.load)
state, selectable_ = self.selector.select(DEFAULT_SELECT_TIMEOUT)
if state == self.selector.TIMEOUT:
continue
elif state == self.selector.ERROR:
syslog.syslog(syslog.LOG_ERR,
"error returned by select")
continue
fd = selectable_.getFd()
# Get the Corresponding subscriber & table
subscriber, table = self.subscriber_map.get(fd, (None, ""))
if not subscriber:
syslog.syslog(syslog.LOG_ERR,
"No Subscriber object found for fd: {}, subscriber map: {}".format(fd, subscriber_map))
continue
key, op, fvs = subscriber.pop()
# Get the registered callback
cbs = self.callbacks.get(table, None)
for callback in cbs:
callback(table, key, op, dict(fvs))
def main(): def main():
@ -1251,7 +1247,6 @@ def main():
signal.signal(signal.SIGHUP, signal_handler) signal.signal(signal.SIGHUP, signal_handler)
daemon = HostConfigDaemon() daemon = HostConfigDaemon()
daemon.register_callbacks() daemon.register_callbacks()
daemon.load()
daemon.start() daemon.start()
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -4,9 +4,10 @@ class MockConfigDb(object):
""" """
STATE_DB = None STATE_DB = None
CONFIG_DB = None CONFIG_DB = None
event_queue = []
def __init__(self, **kwargs): def __init__(self, **kwargs):
pass self.handlers = {}
@staticmethod @staticmethod
def set_config_db(test_config_db): def set_config_db(test_config_db):
@ -44,73 +45,12 @@ class MockConfigDb(object):
def get_table(self, table_name): def get_table(self, table_name):
return MockConfigDb.CONFIG_DB[table_name] return MockConfigDb.CONFIG_DB[table_name]
def subscribe(self, table_name, callback):
self.handlers[table_name] = callback
class MockSelect(): def listen(self, init_data_handler=None):
for e in MockConfigDb.event_queue:
event_queue = [] self.handlers[e[0]](e[0], e[1], self.get_entry(e[0], e[1]))
@staticmethod
def set_event_queue(Q):
MockSelect.event_queue = Q
@staticmethod
def get_event_queue():
return MockSelect.event_queue
@staticmethod
def reset_event_queue():
MockSelect.event_queue = []
def __init__(self):
self.sub_map = {}
self.TIMEOUT = "TIMEOUT"
self.ERROR = "ERROR"
def addSelectable(self, subscriber):
self.sub_map[subscriber.table] = subscriber
def select(self, TIMEOUT):
if not MockSelect.get_event_queue():
raise TimeoutError
table, key = MockSelect.get_event_queue().pop(0)
self.sub_map[table].nextKey(key)
return "OBJECT", self.sub_map[table]
class MockSubscriberStateTable():
FD_INIT = 0
@staticmethod
def generate_fd():
curr = MockSubscriberStateTable.FD_INIT
MockSubscriberStateTable.FD_INIT = curr + 1
return curr
@staticmethod
def reset_fd():
MockSubscriberStateTable.FD_INIT = 0
def __init__(self, conn, table, pop, pri):
self.fd = MockSubscriberStateTable.generate_fd()
self.next_key = ''
self.table = table
def getFd(self):
return self.fd
def nextKey(self, key):
self.next_key = key
def pop(self):
table = MockConfigDb.CONFIG_DB.get(self.table, {})
if self.next_key not in table:
op = "DEL"
fvs = {}
else:
op = "SET"
fvs = table.get(self.next_key, {})
return self.next_key, op, fvs
class MockDBConnector(): class MockDBConnector():

View File

@ -10,8 +10,7 @@ from swsscommon import swsscommon
from parameterized import parameterized from parameterized import parameterized
from unittest import TestCase, mock from unittest import TestCase, mock
from tests.hostcfgd.test_radius_vectors import HOSTCFGD_TEST_RADIUS_VECTOR from tests.hostcfgd.test_radius_vectors import HOSTCFGD_TEST_RADIUS_VECTOR
from tests.common.mock_configdb import MockConfigDb, MockSubscriberStateTable from tests.common.mock_configdb import MockConfigDb, MockDBConnector
from tests.common.mock_configdb import MockSelect, MockDBConnector
test_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) test_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@ -33,12 +32,9 @@ sys.modules['hostcfgd'] = hostcfgd
# Mock swsscommon classes # Mock swsscommon classes
hostcfgd.ConfigDBConnector = MockConfigDb hostcfgd.ConfigDBConnector = MockConfigDb
hostcfgd.SubscriberStateTable = MockSubscriberStateTable
hostcfgd.Select = MockSelect
hostcfgd.DBConnector = MockDBConnector hostcfgd.DBConnector = MockDBConnector
hostcfgd.Table = mock.Mock() hostcfgd.Table = mock.Mock()
class TestHostcfgdRADIUS(TestCase): class TestHostcfgdRADIUS(TestCase):
""" """
Test hostcfd daemon - RADIUS Test hostcfd daemon - RADIUS

View File

@ -10,8 +10,7 @@ from swsscommon import swsscommon
from parameterized import parameterized from parameterized import parameterized
from unittest import TestCase, mock from unittest import TestCase, mock
from tests.hostcfgd.test_tacacs_vectors import HOSTCFGD_TEST_TACACS_VECTOR from tests.hostcfgd.test_tacacs_vectors import HOSTCFGD_TEST_TACACS_VECTOR
from tests.common.mock_configdb import MockConfigDb, MockSubscriberStateTable from tests.common.mock_configdb import MockConfigDb, MockDBConnector
from tests.common.mock_configdb import MockSelect, MockDBConnector
test_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) test_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
modules_path = os.path.dirname(test_path) modules_path = os.path.dirname(test_path)
@ -32,8 +31,6 @@ sys.modules['hostcfgd'] = hostcfgd
# Mock swsscommon classes # Mock swsscommon classes
hostcfgd.ConfigDBConnector = MockConfigDb hostcfgd.ConfigDBConnector = MockConfigDb
hostcfgd.SubscriberStateTable = MockSubscriberStateTable
hostcfgd.Select = MockSelect
hostcfgd.DBConnector = MockDBConnector hostcfgd.DBConnector = MockDBConnector
hostcfgd.Table = mock.Mock() hostcfgd.Table = mock.Mock()

View File

@ -8,8 +8,7 @@ from sonic_py_common.general import load_module_from_source
from unittest import TestCase, mock from unittest import TestCase, mock
from .test_vectors import HOSTCFGD_TEST_VECTOR, HOSTCFG_DAEMON_CFG_DB from .test_vectors import HOSTCFGD_TEST_VECTOR, HOSTCFG_DAEMON_CFG_DB
from tests.common.mock_configdb import MockConfigDb, MockSubscriberStateTable from tests.common.mock_configdb import MockConfigDb, MockDBConnector
from tests.common.mock_configdb import MockSelect, MockDBConnector
from pyfakefs.fake_filesystem_unittest import patchfs from pyfakefs.fake_filesystem_unittest import patchfs
from deepdiff import DeepDiff from deepdiff import DeepDiff
@ -24,8 +23,6 @@ sys.path.insert(0, modules_path)
hostcfgd_path = os.path.join(scripts_path, 'hostcfgd') hostcfgd_path = os.path.join(scripts_path, 'hostcfgd')
hostcfgd = load_module_from_source('hostcfgd', hostcfgd_path) hostcfgd = load_module_from_source('hostcfgd', hostcfgd_path)
hostcfgd.ConfigDBConnector = MockConfigDb hostcfgd.ConfigDBConnector = MockConfigDb
hostcfgd.SubscriberStateTable = MockSubscriberStateTable
hostcfgd.Select = MockSelect
hostcfgd.DBConnector = MockDBConnector hostcfgd.DBConnector = MockDBConnector
hostcfgd.Table = mock.Mock() hostcfgd.Table = mock.Mock()
@ -122,8 +119,8 @@ class TestHostcfgd(TestCase):
feature_handler = hostcfgd.FeatureHandler(MockConfigDb(), feature_state_table_mock, device_config) feature_handler = hostcfgd.FeatureHandler(MockConfigDb(), feature_state_table_mock, device_config)
# sync the state field and Handle Feature Updates # sync the state field and Handle Feature Updates
feature_handler.sync_state_field()
features = MockConfigDb.CONFIG_DB['FEATURE'] features = MockConfigDb.CONFIG_DB['FEATURE']
feature_handler.sync_state_field(features)
for key, fvs in features.items(): for key, fvs in features.items():
feature_handler.handle(key, 'SET', fvs) feature_handler.handle(key, 'SET', fvs)
@ -227,7 +224,7 @@ class TestHostcfgdDaemon(TestCase):
@patchfs @patchfs
def test_feature_events(self, fs): def test_feature_events(self, fs):
fs.create_dir(hostcfgd.FeatureHandler.SYSTEMD_SYSTEM_DIR) fs.create_dir(hostcfgd.FeatureHandler.SYSTEMD_SYSTEM_DIR)
MockSelect.event_queue = [('FEATURE', 'dhcp_relay'), MockConfigDb.event_queue = [('FEATURE', 'dhcp_relay'),
('FEATURE', 'mux'), ('FEATURE', 'mux'),
('FEATURE', 'telemetry')] ('FEATURE', 'telemetry')]
daemon = hostcfgd.HostConfigDaemon() daemon = hostcfgd.HostConfigDaemon()
@ -258,7 +255,7 @@ class TestHostcfgdDaemon(TestCase):
# Change the state to disabled # Change the state to disabled
MockConfigDb.CONFIG_DB['FEATURE']['telemetry']['state'] = 'disabled' MockConfigDb.CONFIG_DB['FEATURE']['telemetry']['state'] = 'disabled'
MockSelect.event_queue = [('FEATURE', 'telemetry')] MockConfigDb.event_queue = [('FEATURE', 'telemetry')]
try: try:
daemon.start() daemon.start()
except TimeoutError: except TimeoutError:
@ -273,7 +270,7 @@ class TestHostcfgdDaemon(TestCase):
def test_loopback_events(self): def test_loopback_events(self):
MockConfigDb.set_config_db(HOSTCFG_DAEMON_CFG_DB) MockConfigDb.set_config_db(HOSTCFG_DAEMON_CFG_DB)
MockSelect.event_queue = [('NTP', 'global'), MockConfigDb.event_queue = [('NTP', 'global'),
('NTP_SERVER', '0.debian.pool.ntp.org'), ('NTP_SERVER', '0.debian.pool.ntp.org'),
('LOOPBACK_INTERFACE', 'Loopback0|10.184.8.233/32')] ('LOOPBACK_INTERFACE', 'Loopback0|10.184.8.233/32')]
daemon = hostcfgd.HostConfigDaemon() daemon = hostcfgd.HostConfigDaemon()
@ -296,8 +293,7 @@ class TestHostcfgdDaemon(TestCase):
MockConfigDb.set_config_db(HOSTCFG_DAEMON_CFG_DB) MockConfigDb.set_config_db(HOSTCFG_DAEMON_CFG_DB)
daemon = hostcfgd.HostConfigDaemon() daemon = hostcfgd.HostConfigDaemon()
daemon.register_callbacks() daemon.register_callbacks()
assert MockConfigDb.CONFIG_DB['KDUMP']['config'] MockConfigDb.event_queue = [('KDUMP', 'config')]
MockSelect.event_queue = [('KDUMP', 'config')]
with mock.patch('hostcfgd.subprocess') as mocked_subprocess: with mock.patch('hostcfgd.subprocess') as mocked_subprocess:
popen_mock = mock.Mock() popen_mock = mock.Mock()
attrs = {'communicate.return_value': ('output', 'error')} attrs = {'communicate.return_value': ('output', 'error')}