From 29be8d2c50d0f51108f56fabe6adc75f7b44fb7b Mon Sep 17 00:00:00 2001 From: Abhishek Dosi Date: Fri, 19 Aug 2022 21:00:09 +0000 Subject: [PATCH 1/4] Added Support to render Feature Table using Device running metadata. Also added support to render 'has_asic_scope' field of Feature Table. Signed-off-by: Abhishek Dosi --- scripts/hostcfgd | 84 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 64 insertions(+), 20 deletions(-) diff --git a/scripts/hostcfgd b/scripts/hostcfgd index a82a630bfc0a..1026527585bf 100755 --- a/scripts/hostcfgd +++ b/scripts/hostcfgd @@ -146,30 +146,31 @@ class Feature(object): """ self.name = feature_name - self.state = self._get_target_state(feature_cfg.get('state'), device_config or {}) + self.state = self._get_feature_table_key_render_value(feature_cfg.get('state'), device_config or {}, ['enabled', 'disabled', 'always_enabled', 'always_disabled']) self.auto_restart = feature_cfg.get('auto_restart', 'disabled') self.has_timer = safe_eval(feature_cfg.get('has_timer', 'False')) self.has_global_scope = safe_eval(feature_cfg.get('has_global_scope', 'True')) - self.has_per_asic_scope = safe_eval(feature_cfg.get('has_per_asic_scope', 'False')) + self.has_per_asic_scope = safe_eval(self._get_feature_table_key_render_value(feature_cfg.get('has_per_asic_scope', 'False'), device_config or {}, ['True', 'False'])) - def _get_target_state(self, state_configuration, device_config): - """ Returns the target state for the feature by rendering the state field as J2 template. + def _get_feature_table_key_render_value(self, configuration, device_config, expected_values): + """ Returns the target value for the feature by rendering the configuration as J2 template. Args: - state_configuration (str): State configuration from CONFIG_DB - deviec_config (dict): DEVICE_METADATA section of CONFIG_DB + configuration (str): Feature Table value from CONFIG_DB for given key + device_config (dict): DEVICE_METADATA section of CONFIG_DB and populated Device Running Metadata + expected_values (list): Expected set of Feature Table value for given key Returns: - (str): Target feature state + (str): Target feature table value for given key """ - if state_configuration is None: + if configuration is None: return None - template = jinja2.Template(state_configuration) - target_state = template.render(device_config) - if target_state not in ('enabled', 'disabled', 'always_enabled', 'always_disabled'): - raise ValueError('Invalid state rendered for feature {}: {}'.format(self.name, target_state)) - return target_state + template = jinja2.Template(configuration) + target_value = template.render(device_config) + if target_value not in expected_values: + raise ValueError('Invalid value rendered for feature {}: {}'.format(self.name, target_value)) + return target_value def compare_state(self, feature_name, feature_cfg): if self.name != feature_name or not isinstance(feature_cfg, dict): @@ -197,6 +198,7 @@ class FeatureHandler(object): self._device_config = device_config self._cached_config = {} self.is_multi_npu = device_info.is_multi_npu() + self._device_running_config = device_info.get_device_runtime_metadata() def handler(self, feature_name, op, feature_cfg): if not feature_cfg: @@ -205,7 +207,7 @@ class FeatureHandler(object): self._feature_state_table._del(feature_name) return - feature = Feature(feature_name, feature_cfg, self._device_config) + feature = Feature(feature_name, feature_cfg, self._device_config | self._device_running_config) self._cached_config.setdefault(feature_name, Feature(feature_name, {})) # Change auto-restart configuration first. @@ -230,14 +232,14 @@ class FeatureHandler(object): """ Summary: 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 and generated Device Running Metadata """ for feature_name in feature_table.keys(): if not feature_name: syslog.syslog(syslog.LOG_WARNING, "Feature is None") continue - feature = Feature(feature_name, feature_table[feature_name], self._device_config) + feature = Feature(feature_name, feature_table[feature_name], self._device_config | self._device_running_config) self._cached_config.setdefault(feature_name, feature) self.update_systemd_config(feature) @@ -283,8 +285,50 @@ class FeatureHandler(object): self.disable_feature(feature) syslog.syslog(syslog.LOG_INFO, "Feature {} is stopped and disabled".format(feature.name)) + if self.is_multi_npu: + self.sync_feature_asic_scope(feature) + return True + def sync_feature_asic_scope(self, feature_config): + """Updates the has_per_asic_scope field in the FEATURE|* tables as the field + might have to be rendered based on DEVICE_METADATA table or Device Running configuration. + Disable the ASIC instance service unit file it the render value is False and update config + + Args: + feature: An object represents a feature's configuration in `FEATURE` + table of `CONFIG_DB`. + + Returns: + None. + """ + + cmds = [] + feature_names, feature_suffixes = self.get_multiasic_feature_instances(feature_config, True) + for feature_name in feature_names: + if '@' not in feature_name: + continue + unit_file_state = self.get_systemd_unit_state("{}.{}".format(feature_name, feature_suffixes[-1])) + if not unit_file_state: + continue + if unit_file_state == "enabled" and not feature_config.has_per_asic_scope: + for suffix in reversed(feature_suffixes): + cmds.append("sudo systemctl stop {}.{}".format(feature_name, suffix)) + cmds.append("sudo systemctl disable {}.{}".format(feature_name, feature_suffixes[-1])) + cmds.append("sudo systemctl mask {}.{}".format(feature_name, feature_suffixes[-1])) + for cmd in cmds: + syslog.syslog(syslog.LOG_INFO, "Running cmd: '{}'".format(cmd)) + try: + run_cmd(cmd, raise_exception=True) + except Exception as err: + syslog.syslog(syslog.LOG_ERR, "Feature '{}.{}' failed to be stopped and disabled" + .format(feature.name, feature_suffixes[-1])) + self.set_feature_state(feature, self.FEATURE_STATE_FAILED) + return + self._config_db.mod_entry('FEATURE', feature_config.name, {'has_per_asic_scope': feature_config.has_per_asic_scope}) + + + def update_systemd_config(self, feature_config): """Updates `Restart=` field in feature's systemd configuration file according to the value of `auto_restart` field in `FEATURE` table of `CONFIG_DB`. @@ -323,12 +367,12 @@ class FeatureHandler(object): except Exception as err: syslog.syslog(syslog.LOG_ERR, "Failed to reload systemd configuration files!") - def get_multiasic_feature_instances(self, feature): + def get_multiasic_feature_instances(self, feature, all_instance=False): # Create feature name suffix depending feature is running in host or namespace or in both feature_names = ( ([feature.name] if feature.has_global_scope or not self.is_multi_npu else []) + ([(feature.name + '@' + str(asic_inst)) for asic_inst in range(device_info.get_num_npus()) - if feature.has_per_asic_scope and self.is_multi_npu]) + if self.is_multi_npu and (all_instance or feature.has_per_asic_scope)]) ) if not feature_names: @@ -358,7 +402,7 @@ class FeatureHandler(object): for feature_name in feature_names: # Check if it is already enabled, if yes skip the system call unit_file_state = self.get_systemd_unit_state("{}.{}".format(feature_name, feature_suffixes[-1])) - if unit_file_state == "enabled": + if unit_file_state == "enabled" or not unit_file_state: continue for suffix in feature_suffixes: @@ -388,7 +432,7 @@ class FeatureHandler(object): for feature_name in feature_names: # Check if it is already disabled, if yes skip the system call unit_file_state = self.get_systemd_unit_state("{}.{}".format(feature_name, feature_suffixes[-1])) - if unit_file_state in ("disabled", "masked"): + if unit_file_state in ("disabled", "masked") or not unit_file_state: continue for suffix in reversed(feature_suffixes): From 7b8c7d10234973140935a35e919156080d531e7a Mon Sep 17 00:00:00 2001 From: Abhishek Dosi Date: Tue, 30 Aug 2022 20:53:36 +0000 Subject: [PATCH 2/4] Added UT for the changes Signed-off-by: Abhishek Dosi --- scripts/hostcfgd | 9 +- tests/hostcfgd/hostcfgd_test.py | 122 +++++--- tests/hostcfgd/test_vectors.py | 528 +++++++++++++++++++++++++++++++- 3 files changed, 602 insertions(+), 57 deletions(-) diff --git a/scripts/hostcfgd b/scripts/hostcfgd index 1026527585bf..f961875fa139 100755 --- a/scripts/hostcfgd +++ b/scripts/hostcfgd @@ -232,7 +232,7 @@ class FeatureHandler(object): """ Summary: Updates the state field in the FEATURE|* tables as the state field - might have to be rendered based on DEVICE_METADATA table and generated Device Running Metadata + might have to be rendere dbased on DEVICE_METADATA table and generated Device Running Metadata """ for feature_name in feature_table.keys(): if not feature_name: @@ -285,8 +285,7 @@ class FeatureHandler(object): self.disable_feature(feature) syslog.syslog(syslog.LOG_INFO, "Feature {} is stopped and disabled".format(feature.name)) - if self.is_multi_npu: - self.sync_feature_asic_scope(feature) + self.sync_feature_asic_scope(feature) return True @@ -311,7 +310,7 @@ class FeatureHandler(object): unit_file_state = self.get_systemd_unit_state("{}.{}".format(feature_name, feature_suffixes[-1])) if not unit_file_state: continue - if unit_file_state == "enabled" and not feature_config.has_per_asic_scope: + if unit_file_state != "disabled" and not feature_config.has_per_asic_scope: for suffix in reversed(feature_suffixes): cmds.append("sudo systemctl stop {}.{}".format(feature_name, suffix)) cmds.append("sudo systemctl disable {}.{}".format(feature_name, feature_suffixes[-1])) @@ -325,7 +324,7 @@ class FeatureHandler(object): .format(feature.name, feature_suffixes[-1])) self.set_feature_state(feature, self.FEATURE_STATE_FAILED) return - self._config_db.mod_entry('FEATURE', feature_config.name, {'has_per_asic_scope': feature_config.has_per_asic_scope}) + self._config_db.mod_entry('FEATURE', feature_config.name, {'has_per_asic_scope': str(feature_config.has_per_asic_scope)}) diff --git a/tests/hostcfgd/hostcfgd_test.py b/tests/hostcfgd/hostcfgd_test.py index 786bd1c8f2a9..e7434fd380e7 100644 --- a/tests/hostcfgd/hostcfgd_test.py +++ b/tests/hostcfgd/hostcfgd_test.py @@ -1,6 +1,7 @@ import os import sys import swsscommon as swsscommon_package +from sonic_py_common import device_info from swsscommon import swsscommon from parameterized import parameterized @@ -45,7 +46,7 @@ def checks_config_table(self, feature_table, expected_table): return True if not ddiff else False - def checks_systemd_config_file(self, feature_table): + def checks_systemd_config_file(self, feature_table, feature_systemd_name_map=None): """Checks whether the systemd configuration file of each feature was created or not and whether the `Restart=` field in the file is set correctly or not. @@ -68,13 +69,16 @@ def checks_systemd_config_file(self, feature_table): elif "disabled" in auto_restart_status: auto_restart_status = "disabled" - feature_systemd_config_file_path = systemd_config_file_path.format(feature_name) - is_config_file_existing = os.path.exists(feature_systemd_config_file_path) - assert is_config_file_existing, "Systemd configuration file of feature '{}' does not exist!".format(feature_name) + feature_systemd_list = feature_systemd_name_map[feature_name] if feature_systemd_name_map else [feature_name] - with open(feature_systemd_config_file_path) as systemd_config_file: - status = systemd_config_file.read().strip() - assert status == '[Service]\nRestart={}'.format(truth_table[auto_restart_status]) + for feature_systemd in feature_systemd_list: + feature_systemd_config_file_path = systemd_config_file_path.format(feature_systemd) + is_config_file_existing = os.path.exists(feature_systemd_config_file_path) + assert is_config_file_existing, "Systemd configuration file of feature '{}' does not exist!".format(feature_systemd) + + with open(feature_systemd_config_file_path) as systemd_config_file: + status = systemd_config_file.read().strip() + assert status == '[Service]\nRestart={}'.format(truth_table[auto_restart_status]) def get_state_db_set_calls(self, feature_table): """Returns a Mock call objects which recorded the `set` calls to `FEATURE` table in `STATE_DB`. @@ -119,31 +123,40 @@ def test_sync_state_field(self, test_scenario_name, config_data, fs): MockConfigDb.set_config_db(config_data['config_db']) feature_state_table_mock = mock.Mock() with mock.patch('hostcfgd.subprocess') as mocked_subprocess: - 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'] - feature_handler = hostcfgd.FeatureHandler(MockConfigDb(), feature_state_table_mock, device_config) - - feature_table = MockConfigDb.CONFIG_DB['FEATURE'] - feature_handler.sync_state_field(feature_table) - - 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']) - 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']) + 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'] + 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 | config_data['device_runtime_metadata']) + 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) @parameterized.expand(HOSTCFGD_TEST_VECTOR) @patchfs @@ -164,25 +177,32 @@ def test_handler(self, test_scenario_name, config_data, fs): MockConfigDb.set_config_db(config_data['config_db']) feature_state_table_mock = mock.Mock() with mock.patch('hostcfgd.subprocess') as mocked_subprocess: - 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'] - feature_handler = hostcfgd.FeatureHandler(MockConfigDb(), feature_state_table_mock, device_config) - - feature_table = MockConfigDb.CONFIG_DB['FEATURE'] - - for feature_name, feature_config in feature_table.items(): - feature_handler.handler(feature_name, 'SET', feature_config) - - self.checks_systemd_config_file(config_data['config_db']['FEATURE']) - 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) + 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'] + feature_handler = hostcfgd.FeatureHandler(MockConfigDb(), feature_state_table_mock, device_config) + + feature_table = MockConfigDb.CONFIG_DB['FEATURE'] + + feature_systemd_name_map = {} + for feature_name, feature_config in feature_table.items(): + feature_handler.handler(feature_name, 'SET', feature_config) + feature = hostcfgd.Feature(feature_name, feature_table[feature_name], device_config | config_data['device_runtime_metadata']) + feature_names, _ = feature_handler.get_multiasic_feature_instances(feature) + feature_systemd_name_map[feature_name] = feature_names + + 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) def test_feature_config_parsing(self): swss_feature = hostcfgd.Feature('swss', { diff --git a/tests/hostcfgd/test_vectors.py b/tests/hostcfgd/test_vectors.py index 43754252c0e3..c655bbe6f127 100644 --- a/tests/hostcfgd/test_vectors.py +++ b/tests/hostcfgd/test_vectors.py @@ -7,6 +7,11 @@ [ "DualTorCase", { + "device_runtime_metadata": { + "DEVICE_RUNTIME_METADATA": { + "ETHERNET_PORTS_PRESENT":True + } + }, "config_db": { "DEVICE_METADATA": { "localhost": { @@ -107,6 +112,11 @@ [ "SingleToRCase", { + "device_runtime_metadata": { + "DEVICE_RUNTIME_METADATA": { + "ETHERNET_PORTS_PRESENT":True + } + }, "config_db": { "DEVICE_METADATA": { "localhost": { @@ -224,6 +234,11 @@ [ "T1Case", { + "device_runtime_metadata": { + "DEVICE_RUNTIME_METADATA": { + "ETHERNET_PORTS_PRESENT":True + } + }, "config_db": { "DEVICE_METADATA": { "localhost": { @@ -320,6 +335,11 @@ [ "SingleToRCase_DHCP_Relay_Enabled", { + "device_runtime_metadata": { + "DEVICE_RUNTIME_METADATA": { + "ETHERNET_PORTS_PRESENT":True + } + }, "config_db": { "DEVICE_METADATA": { "localhost": { @@ -419,6 +439,11 @@ [ "DualTorCaseWithNoSystemCalls", { + "device_runtime_metadata": { + "DEVICE_RUNTIME_METADATA": { + "ETHERNET_PORTS_PRESENT":True + } + }, "config_db": { "DEVICE_METADATA": { "localhost": { @@ -504,7 +529,508 @@ 'communicate.return_value': ('enabled', 'error') }, } - ] + ], + [ + "Chassis_Supervisor_PACKET", + { + "device_runtime_metadata": { + "DEVICE_RUNTIME_METADATA": { + "CHASSIS_METADATA": { + "module_type": "supervisor", + "chassis_type": "packet" + }, + "ETHERNET_PORTS_PRESENT":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": "True", + "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": "True", + "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" + }, + + + }, + }, + "expected_config_db": { + "FEATURE": { + "bgp": { + "auto_restart": "enabled", + "has_global_scope": "True", + "has_per_asic_scope": "True", + "has_timer": "False", + "high_mem_alert": "disabled", + "state": "disabled" + }, + "teamd": { + "auto_restart": "enabled", + "has_global_scope": "True", + "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": "False", + "has_timer": "False", + "high_mem_alert": "disabled", + "state": "enabled" + }, + }, + }, + "enable_feature_subprocess_calls": [ + call("sudo systemctl stop bgp.service", shell=True), + call("sudo systemctl disable bgp.service", shell=True), + call("sudo systemctl mask bgp.service", shell=True), + ], + "daemon_reload_subprocess_call": [ + call("sudo systemctl daemon-reload", shell=True), + ], + "popen_attributes": { + 'communicate.return_value': ('output', 'error') + }, + }, + ], + [ + "Chassis_Supervisor_VOQ", + { + "device_runtime_metadata": { + "DEVICE_RUNTIME_METADATA": { + "CHASSIS_METADATA": { + "module_type": "supervisor", + "chassis_type": "voq" + }, + "ETHERNET_PORTS_PRESENT":False + } + }, + "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": "True", + "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": "True", + "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" + }, + + + }, + }, + "expected_config_db": { + "FEATURE": { + "bgp": { + "auto_restart": "enabled", + "has_global_scope": "True", + "has_per_asic_scope": "True", + "has_timer": "False", + "high_mem_alert": "disabled", + "state": "disabled" + }, + "teamd": { + "auto_restart": "enabled", + "has_global_scope": "True", + "has_per_asic_scope": "True", + "has_timer": "False", + "high_mem_alert": "disabled", + "state": "disabled" + }, + "lldp": { + "auto_restart": "enabled", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "has_timer": "False", + "high_mem_alert": "disabled", + "state": "enabled" + }, + }, + }, + "enable_feature_subprocess_calls": [ + call("sudo systemctl stop bgp.service", shell=True), + call("sudo systemctl disable bgp.service", shell=True), + call("sudo systemctl mask bgp.service", shell=True), + ], + "daemon_reload_subprocess_call": [ + call("sudo systemctl daemon-reload", shell=True), + ], + "popen_attributes": { + 'communicate.return_value': ('output', 'error') + }, + }, + ], + [ + "Chassis_LineCard_VOQ", + { + "device_runtime_metadata": { + "DEVICE_RUNTIME_METADATA": { + "CHASSIS_METADATA": { + "module_type": "linecard", + "chassis_type": "voq" + }, + "ETHERNET_PORTS_PRESENT":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": "True", + "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": "True", + "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" + }, + + + }, + }, + "expected_config_db": { + "FEATURE": { + "bgp": { + "auto_restart": "enabled", + "has_global_scope": "True", + "has_per_asic_scope": "True", + "has_timer": "False", + "high_mem_alert": "disabled", + "state": "enabled" + }, + "teamd": { + "auto_restart": "enabled", + "has_global_scope": "True", + "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" + }, + }, + }, + "enable_feature_subprocess_calls": [ + call("sudo systemctl start bgp.service", shell=True), + call("sudo systemctl enable bgp.service", shell=True), + call("sudo systemctl unmask bgp.service", shell=True), + call("sudo systemctl start teamd.service", shell=True), + call("sudo systemctl enable teamd.service", shell=True), + call("sudo systemctl unmask teamd.service", shell=True), + + ], + "daemon_reload_subprocess_call": [ + call("sudo systemctl daemon-reload", shell=True), + ], + "popen_attributes": { + 'communicate.return_value': ('output', 'error') + }, + }, + ], + [ + "Chassis_LineCard_Packet", + { + "device_runtime_metadata": { + "DEVICE_RUNTIME_METADATA": { + "CHASSIS_METADATA": { + "module_type": "linecard", + "chassis_type": "packet" + }, + "ETHERNET_PORTS_PRESENT":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": "True", + "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": "True", + "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" + }, + + + }, + }, + "expected_config_db": { + "FEATURE": { + "bgp": { + "auto_restart": "enabled", + "has_global_scope": "True", + "has_per_asic_scope": "True", + "has_timer": "False", + "high_mem_alert": "disabled", + "state": "enabled" + }, + "teamd": { + "auto_restart": "enabled", + "has_global_scope": "True", + "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" + }, + }, + }, + "enable_feature_subprocess_calls": [ + call("sudo systemctl start bgp.service", shell=True), + call("sudo systemctl enable bgp.service", shell=True), + call("sudo systemctl unmask bgp.service", shell=True), + call("sudo systemctl start teamd.service", shell=True), + call("sudo systemctl enable teamd.service", shell=True), + call("sudo systemctl unmask teamd.service", shell=True), + + ], + "daemon_reload_subprocess_call": [ + call("sudo systemctl daemon-reload", shell=True), + ], + "popen_attributes": { + 'communicate.return_value': ('output', 'error') + }, + }, + ], + [ + "Chassis_Supervisor_PACKET_multinpu", + { + "num_npu": 2, + "device_runtime_metadata": { + "DEVICE_RUNTIME_METADATA": { + "CHASSIS_METADATA": { + "module_type": "supervisor", + "chassis_type": "packet" + }, + "ETHERNET_PORTS_PRESENT":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" + }, + + + }, + }, + "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": "disabled" + }, + "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": "False", + "has_timer": "False", + "high_mem_alert": "disabled", + "state": "enabled" + }, + }, + }, + "enable_feature_subprocess_calls": [ + call("sudo systemctl stop bgp@0.service", shell=True), + call("sudo systemctl disable bgp@0.service", shell=True), + call("sudo systemctl mask bgp@0.service", shell=True), + call("sudo systemctl stop bgp@1.service", shell=True), + call("sudo systemctl disable bgp@1.service", shell=True), + call("sudo systemctl mask bgp@1.service", shell=True), + call("sudo systemctl start teamd@0.service", shell=True), + call("sudo systemctl enable teamd@0.service", shell=True), + call("sudo systemctl unmask teamd@0.service", shell=True), + call("sudo systemctl start teamd@1.service", shell=True), + call("sudo systemctl enable teamd@1.service", shell=True), + call("sudo systemctl unmask teamd@1.service", shell=True), + call("sudo systemctl stop lldp@0.service", shell=True), + call("sudo systemctl disable lldp@0.service", shell=True), + call("sudo systemctl mask lldp@0.service", shell=True), + call("sudo systemctl stop lldp@1.service", shell=True), + call("sudo systemctl disable lldp@1.service", shell=True), + call("sudo systemctl mask lldp@1.service", shell=True), + + ], + "daemon_reload_subprocess_call": [ + call("sudo systemctl daemon-reload", shell=True), + ], + "popen_attributes": { + 'communicate.return_value': ('output', 'error') + }, + }, + ], + ] HOSTCFG_DAEMON_CFG_DB = { From 978afb5a92b1953bad8284eb838ba87bfecd0d5f Mon Sep 17 00:00:00 2001 From: Abhishek Dosi Date: Tue, 30 Aug 2022 21:00:05 +0000 Subject: [PATCH 3/4] Aligning Code Signed-off-by: Abhishek Dosi --- scripts/hostcfgd | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/hostcfgd b/scripts/hostcfgd index c761af8b1724..beb5534ad68f 100755 --- a/scripts/hostcfgd +++ b/scripts/hostcfgd @@ -233,7 +233,7 @@ class FeatureHandler(object): """ Summary: Updates the state field in the FEATURE|* tables as the state field - might have to be rendere dbased on DEVICE_METADATA table and generated Device Running Metadata + might have to be rendered based on DEVICE_METADATA table and generated Device Running Metadata """ for feature_name in feature_table.keys(): if not feature_name: @@ -326,9 +326,7 @@ 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)}) - - - + def update_systemd_config(self, feature_config): """Updates `Restart=` field in feature's systemd configuration file according to the value of `auto_restart` field in `FEATURE` table of `CONFIG_DB`. From 17d44c29a071afa1773c3784760728a3a103c3e6 Mon Sep 17 00:00:00 2001 From: Abhishek Dosi Date: Tue, 30 Aug 2022 21:33:54 +0000 Subject: [PATCH 4/4] Made Changes to be Python 3.7 compatible Signed-off-by: Abhishek Dosi --- scripts/hostcfgd | 12 ++++++++++-- tests/hostcfgd/hostcfgd_test.py | 7 +++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/scripts/hostcfgd b/scripts/hostcfgd index beb5534ad68f..d549d560bc83 100755 --- a/scripts/hostcfgd +++ b/scripts/hostcfgd @@ -208,7 +208,11 @@ class FeatureHandler(object): self._feature_state_table._del(feature_name) return - feature = Feature(feature_name, feature_cfg, self._device_config | self._device_running_config) + device_config = {} + device_config.update(self._device_config) + device_config.update(self._device_running_config) + + feature = Feature(feature_name, feature_cfg, device_config) self._cached_config.setdefault(feature_name, Feature(feature_name, {})) # Change auto-restart configuration first. @@ -239,8 +243,12 @@ class FeatureHandler(object): if not feature_name: syslog.syslog(syslog.LOG_WARNING, "Feature is None") continue + + device_config = {} + device_config.update(self._device_config) + device_config.update(self._device_running_config) - feature = Feature(feature_name, feature_table[feature_name], self._device_config | self._device_running_config) + feature = Feature(feature_name, feature_table[feature_name], device_config) self._cached_config.setdefault(feature_name, feature) self.update_systemd_config(feature) diff --git a/tests/hostcfgd/hostcfgd_test.py b/tests/hostcfgd/hostcfgd_test.py index 23fc4a9ddc39..93ba7b9666a6 100644 --- a/tests/hostcfgd/hostcfgd_test.py +++ b/tests/hostcfgd/hostcfgd_test.py @@ -134,6 +134,8 @@ def test_sync_state_field(self, test_scenario_name, config_data, fs): 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'] @@ -141,7 +143,7 @@ def test_sync_state_field(self, test_scenario_name, config_data, fs): feature_systemd_name_map = {} for feature_name in feature_table.keys(): - feature = hostcfgd.Feature(feature_name, feature_table[feature_name], device_config | config_data['device_runtime_metadata']) + 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 @@ -188,6 +190,7 @@ def test_handler(self, test_scenario_name, config_data, fs): 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'] @@ -195,7 +198,7 @@ def test_handler(self, test_scenario_name, config_data, fs): feature_systemd_name_map = {} for feature_name, feature_config in feature_table.items(): feature_handler.handler(feature_name, 'SET', feature_config) - feature = hostcfgd.Feature(feature_name, feature_table[feature_name], device_config | config_data['device_runtime_metadata']) + 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