Skip to content

Commit

Permalink
Added Support to render Feature Table using Device running metadata.
Browse files Browse the repository at this point in the history
Also added support to render 'has_asic_scope' field of Feature Table.

Signed-off-by: Abhishek Dosi <abdosi@microsoft.com>
  • Loading branch information
abdosi committed Aug 19, 2022
1 parent f6ea036 commit 29be8d2
Showing 1 changed file with 64 additions and 20 deletions.
84 changes: 64 additions & 20 deletions scripts/hostcfgd
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Expand All @@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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):
Expand Down

0 comments on commit 29be8d2

Please sign in to comment.