diff --git a/scripts/hostcfgd b/scripts/hostcfgd index d549d560bc83..53cd841df474 100755 --- a/scripts/hostcfgd +++ b/scripts/hostcfgd @@ -11,7 +11,7 @@ import signal import re import jinja2 from sonic_py_common import device_info -from swsscommon.swsscommon import ConfigDBConnector, DBConnector, Table +from swsscommon.swsscommon import ConfigDBConnector, DBConnector, Table, SonicDBConfig from swsscommon import swsscommon # FILE @@ -200,6 +200,21 @@ class FeatureHandler(object): self._cached_config = {} self.is_multi_npu = device_info.is_multi_npu() self._device_running_config = device_info.get_device_runtime_metadata() + self.ns_cfg_db = {} + self.ns_feature_state_tbl = {} + + # Initlaize Global config that loads all database*.json + if self.is_multi_npu: + SonicDBConfig.initializeGlobalConfig() + namespaces = device_info.get_namespaces() + for ns in namespaces: + #Connect to ConfigDB in each namespace + self.ns_cfg_db[ns] = ConfigDBConnector(namespace=ns) + self.ns_cfg_db[ns].connect(wait_for_init=True, retry_on=True) + + #Connect to stateDB in each namespace + db_conn = DBConnector(STATE_DB, 0, False, ns); + self.ns_feature_state_tbl[ns] = Table(db_conn, 'FEATURE') def handler(self, feature_name, op, feature_cfg): if not feature_cfg: @@ -247,7 +262,6 @@ class FeatureHandler(object): device_config = {} device_config.update(self._device_config) device_config.update(self._device_running_config) - feature = Feature(feature_name, feature_table[feature_name], device_config) self._cached_config.setdefault(feature_name, feature) @@ -334,6 +348,10 @@ class FeatureHandler(object): self.set_feature_state(feature, self.FEATURE_STATE_FAILED) return self._config_db.mod_entry('FEATURE', feature_config.name, {'has_per_asic_scope': str(feature_config.has_per_asic_scope)}) + + # sync has_per_asic_scope to CONFIG_DB in namespaces in multi-asic platform + for ns, db in self.ns_cfg_db.items(): + db.mod_entry('FEATURE', feature_config.name, {'has_per_asic_scope': str(feature_config.has_per_asic_scope)}) def update_systemd_config(self, feature_config): """Updates `Restart=` field in feature's systemd configuration file @@ -460,9 +478,16 @@ class FeatureHandler(object): def resync_feature_state(self, feature): self._config_db.mod_entry('FEATURE', feature.name, {'state': feature.state}) + # resync the feature state to CONFIG_DB in namespaces in multi-asic platform + for ns, db in self.ns_cfg_db.items(): + db.mod_entry('FEATURE', feature.name, {'state': feature.state}) + def set_feature_state(self, feature, state): self._feature_state_table.set(feature.name, [('state', state)]) + # Update the feature state to STATE_DB in namespaces in multi-asic platform + for ns, tbl in self.ns_feature_state_tbl.items(): + tbl.set(feature.name, [('state', state)]) class Iptables(object): def __init__(self): diff --git a/tests/common/mock_configdb.py b/tests/common/mock_configdb.py index 1ee279a6b9ae..70b8da6afabe 100644 --- a/tests/common/mock_configdb.py +++ b/tests/common/mock_configdb.py @@ -57,5 +57,5 @@ def listen(self, init_data_handler=None): class MockDBConnector(): - def __init__(self, db, val): + def __init__(self, db, val, tcpFlag=False, name=None): pass diff --git a/tests/hostcfgd/hostcfgd_test.py b/tests/hostcfgd/hostcfgd_test.py index 93ba7b9666a6..bc79aeb164f8 100644 --- a/tests/hostcfgd/hostcfgd_test.py +++ b/tests/hostcfgd/hostcfgd_test.py @@ -28,7 +28,6 @@ hostcfgd.DBConnector = MockDBConnector hostcfgd.Table = mock.Mock() - class TestFeatureHandler(TestCase): """Test methods of `FeatureHandler` class. """ @@ -127,39 +126,46 @@ def test_sync_state_field(self, test_scenario_name, config_data, fs): with mock.patch("sonic_py_common.device_info.get_device_runtime_metadata", return_value=config_data['device_runtime_metadata']): with mock.patch("sonic_py_common.device_info.is_multi_npu", return_value=True if 'num_npu' in config_data else False): with mock.patch("sonic_py_common.device_info.get_num_npus", return_value=config_data['num_npu'] if 'num_npu' in config_data else 1): - popen_mock = mock.Mock() - attrs = config_data['popen_attributes'] - popen_mock.configure_mock(**attrs) - mocked_subprocess.Popen.return_value = popen_mock - - device_config = {} - device_config['DEVICE_METADATA'] = MockConfigDb.CONFIG_DB['DEVICE_METADATA'] - device_config.update(config_data['device_runtime_metadata']) - - feature_handler = hostcfgd.FeatureHandler(MockConfigDb(), feature_state_table_mock, device_config) - - feature_table = MockConfigDb.CONFIG_DB['FEATURE'] - feature_handler.sync_state_field(feature_table) - - feature_systemd_name_map = {} - for feature_name in feature_table.keys(): - feature = hostcfgd.Feature(feature_name, feature_table[feature_name], device_config) - feature_names, _ = feature_handler.get_multiasic_feature_instances(feature) - feature_systemd_name_map[feature_name] = feature_names - - is_any_difference = self.checks_config_table(MockConfigDb.get_config_db()['FEATURE'], - config_data['expected_config_db']['FEATURE']) - assert is_any_difference, "'FEATURE' table in 'CONFIG_DB' is modified unexpectedly!" - - feature_table_state_db_calls = self.get_state_db_set_calls(feature_table) - - self.checks_systemd_config_file(config_data['config_db']['FEATURE'], feature_systemd_name_map) - mocked_subprocess.check_call.assert_has_calls(config_data['enable_feature_subprocess_calls'], - any_order=True) - mocked_subprocess.check_call.assert_has_calls(config_data['daemon_reload_subprocess_call'], - any_order=True) - feature_state_table_mock.set.assert_has_calls(feature_table_state_db_calls) - self.checks_systemd_config_file(config_data['config_db']['FEATURE'], feature_systemd_name_map) + with mock.patch("sonic_py_common.device_info.get_namespaces", return_value=["asic{}".format(a) for a in range(config_data['num_npu'])] if 'num_npu' in config_data else []): + popen_mock = mock.Mock() + attrs = config_data['popen_attributes'] + popen_mock.configure_mock(**attrs) + mocked_subprocess.Popen.return_value = popen_mock + + device_config = {} + device_config['DEVICE_METADATA'] = MockConfigDb.CONFIG_DB['DEVICE_METADATA'] + device_config.update(config_data['device_runtime_metadata']) + + feature_handler = hostcfgd.FeatureHandler(MockConfigDb(), feature_state_table_mock, device_config) + feature_table = MockConfigDb.CONFIG_DB['FEATURE'] + feature_handler.sync_state_field(feature_table) + + feature_systemd_name_map = {} + for feature_name in feature_table.keys(): + feature = hostcfgd.Feature(feature_name, feature_table[feature_name], device_config) + feature_names, _ = feature_handler.get_multiasic_feature_instances(feature) + feature_systemd_name_map[feature_name] = feature_names + + is_any_difference = self.checks_config_table(MockConfigDb.get_config_db()['FEATURE'], + config_data['expected_config_db']['FEATURE']) + assert is_any_difference, "'FEATURE' table in 'CONFIG_DB' is modified unexpectedly!" + + if 'num_npu' in config_data: + for ns in range(config_data['num_npu']): + namespace = "asic{}".format(ns) + is_any_difference = self.checks_config_table(feature_handler.ns_cfg_db[namespace].get_config_db()['FEATURE'], + config_data['expected_config_db']['FEATURE']) + assert is_any_difference, "'FEATURE' table in 'CONFIG_DB' in namespace {} is modified unexpectedly!".format(namespace) + + feature_table_state_db_calls = self.get_state_db_set_calls(feature_table) + + self.checks_systemd_config_file(config_data['config_db']['FEATURE'], feature_systemd_name_map) + mocked_subprocess.check_call.assert_has_calls(config_data['enable_feature_subprocess_calls'], + any_order=True) + mocked_subprocess.check_call.assert_has_calls(config_data['daemon_reload_subprocess_call'], + any_order=True) + feature_state_table_mock.set.assert_has_calls(feature_table_state_db_calls) + self.checks_systemd_config_file(config_data['config_db']['FEATURE'], feature_systemd_name_map) @parameterized.expand(HOSTCFGD_TEST_VECTOR) @patchfs diff --git a/tests/hostcfgd/test_vectors.py b/tests/hostcfgd/test_vectors.py index 4f6dca3056a6..0a39b48053d1 100644 --- a/tests/hostcfgd/test_vectors.py +++ b/tests/hostcfgd/test_vectors.py @@ -1030,6 +1030,141 @@ }, }, ], + [ + "Chassis_LineCard_VOQ_multinpu", + { + "num_npu": 2, + "device_runtime_metadata": { + "DEVICE_RUNTIME_METADATA": { + "CHASSIS_METADATA": { + "module_type": "linecard", + "chassis_type": "voq" + }, + "ETHERNET_PORTS_PRESENT":True, + "MACSEC_SUPPORTED":True + } + }, + "config_db": { + "DEVICE_METADATA": { + "localhost": { + "type": "SpineRouter", + } + }, + "KDUMP": { + "config": { + "enabled": "false", + "num_dumps": "3", + "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" + } + }, + "FEATURE": { + "bgp": { + "state": "{% if not DEVICE_RUNTIME_METADATA['ETHERNET_PORTS_PRESENT'] or ('CHASSIS_METADATA' in DEVICE_RUNTIME_METADATA and DEVICE_RUNTIME_METADATA['CHASSIS_METADATA']['module_type'] in ['supervisor']) %}disabled{% else %}enabled{% endif %}", + "has_timer": "False", + "has_global_scope": "False", + "has_per_asic_scope": "True", + "auto_restart": "enabled", + "high_mem_alert": "disabled" + }, + "teamd": { + "state": "{% if not DEVICE_RUNTIME_METADATA['ETHERNET_PORTS_PRESENT'] %}disabled{% else %}enabled{% endif %}", + "has_timer": "False", + "has_global_scope": "False", + "has_per_asic_scope": "True", + "auto_restart": "enabled", + "high_mem_alert": "disabled" + }, + "lldp": { + "state": "enabled", + "has_timer": "False", + "has_global_scope": "True", + "has_per_asic_scope": "{% if not DEVICE_RUNTIME_METADATA['ETHERNET_PORTS_PRESENT'] or ('CHASSIS_METADATA' in DEVICE_RUNTIME_METADATA and DEVICE_RUNTIME_METADATA['CHASSIS_METADATA']['module_type'] in ['supervisor']) %}False{% else %}True{% endif %}", + "auto_restart": "enabled", + "high_mem_alert": "disabled" + }, + "macsec": { + "state": "{% if 'type' in DEVICE_METADATA['localhost'] and DEVICE_METADATA['localhost']['type'] == 'SpineRouter' and DEVICE_RUNTIME_METADATA['MACSEC_SUPPORTED'] %}enabled{% else %}disabled{% endif %}", + "has_timer": "False", + "has_global_scope": "False", + "has_per_asic_scope": "True", + "auto_restart": "enabled", + "high_mem_alert": "disabled" + } + }, + }, + "expected_config_db": { + "FEATURE": { + "bgp": { + "auto_restart": "enabled", + "has_global_scope": "False", + "has_per_asic_scope": "True", + "has_timer": "False", + "high_mem_alert": "disabled", + "state": "enabled" + }, + "teamd": { + "auto_restart": "enabled", + "has_global_scope": "False", + "has_per_asic_scope": "True", + "has_timer": "False", + "high_mem_alert": "disabled", + "state": "enabled" + }, + "lldp": { + "auto_restart": "enabled", + "has_global_scope": "True", + "has_per_asic_scope": "True", + "has_timer": "False", + "high_mem_alert": "disabled", + "state": "enabled" + }, + "macsec": { + "auto_restart": "enabled", + "has_global_scope": "False", + "has_per_asic_scope": "True", + "has_timer": "False", + "high_mem_alert": "disabled", + "state": "enabled" + } + }, + }, + "enable_feature_subprocess_calls": [ + call('sudo systemctl unmask bgp@0.service', shell=True), + call('sudo systemctl enable bgp@0.service', shell=True), + call('sudo systemctl start bgp@0.service', shell=True), + call('sudo systemctl unmask bgp@1.service', shell=True), + call('sudo systemctl enable bgp@1.service', shell=True), + call('sudo systemctl start bgp@1.service', shell=True), + call('sudo systemctl unmask teamd@0.service', shell=True), + call('sudo systemctl enable teamd@0.service', shell=True), + call('sudo systemctl start teamd@0.service', shell=True), + call('sudo systemctl unmask teamd@1.service', shell=True), + call('sudo systemctl enable teamd@1.service', shell=True), + call('sudo systemctl start teamd@1.service', shell=True), + call('sudo systemctl unmask lldp.service', shell=True), + call('sudo systemctl enable lldp.service', shell=True), + call('sudo systemctl start lldp.service', shell=True), + call('sudo systemctl unmask lldp@0.service', shell=True), + call('sudo systemctl enable lldp@0.service', shell=True), + call('sudo systemctl start lldp@0.service', shell=True), + call('sudo systemctl unmask lldp@1.service', shell=True), + call('sudo systemctl enable lldp@1.service', shell=True), + call('sudo systemctl start lldp@1.service', shell=True), + call('sudo systemctl unmask macsec@0.service', shell=True), + call('sudo systemctl enable macsec@0.service', shell=True), + call('sudo systemctl start macsec@0.service', shell=True), + call('sudo systemctl unmask macsec@1.service', shell=True), + call('sudo systemctl enable macsec@1.service', shell=True), + call('sudo systemctl start macsec@1.service', shell=True) + ], + "daemon_reload_subprocess_call": [ + call("sudo systemctl daemon-reload", shell=True), + ], + "popen_attributes": { + 'communicate.return_value': ('output', 'error') + }, + }, + ] ]