@@ -146,30 +146,31 @@ class Feature(object):
146146 """
147147
148148 self .name = feature_name
149- self .state = self ._get_target_state (feature_cfg .get ('state' ), device_config or {})
149+ self .state = self ._get_feature_table_key_render_value (feature_cfg .get ('state' ), device_config or {}, [ 'enabled' , 'disabled' , 'always_enabled' , 'always_disabled' ] )
150150 self .auto_restart = feature_cfg .get ('auto_restart' , 'disabled' )
151151 self .has_timer = safe_eval (feature_cfg .get ('has_timer' , 'False' ))
152152 self .has_global_scope = safe_eval (feature_cfg .get ('has_global_scope' , 'True' ))
153- self .has_per_asic_scope = safe_eval (feature_cfg .get ('has_per_asic_scope' , 'False' ))
153+ 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' ] ))
154154
155- def _get_target_state (self , state_configuration , device_config ):
156- """ Returns the target state for the feature by rendering the state field as J2 template.
155+ def _get_feature_table_key_render_value (self , configuration , device_config , expected_values ):
156+ """ Returns the target value for the feature by rendering the configuration as J2 template.
157157
158158 Args:
159- state_configuration (str): State configuration from CONFIG_DB
160- deviec_config (dict): DEVICE_METADATA section of CONFIG_DB
159+ configuration (str): Feature Table value from CONFIG_DB for given key
160+ device_config (dict): DEVICE_METADATA section of CONFIG_DB and populated Device Running Metadata
161+ expected_values (list): Expected set of Feature Table value for given key
161162 Returns:
162- (str): Target feature state
163+ (str): Target feature table value for given key
163164 """
164165
165- if state_configuration is None :
166+ if configuration is None :
166167 return None
167168
168- template = jinja2 .Template (state_configuration )
169- target_state = template .render (device_config )
170- if target_state not in ( 'enabled' , 'disabled' , 'always_enabled' , 'always_disabled' ) :
171- raise ValueError ('Invalid state rendered for feature {}: {}' .format (self .name , target_state ))
172- return target_state
169+ template = jinja2 .Template (configuration )
170+ target_value = template .render (device_config )
171+ if target_value not in expected_values :
172+ raise ValueError ('Invalid value rendered for feature {}: {}' .format (self .name , target_value ))
173+ return target_value
173174
174175 def compare_state (self , feature_name , feature_cfg ):
175176 if self .name != feature_name or not isinstance (feature_cfg , dict ):
@@ -197,6 +198,7 @@ class FeatureHandler(object):
197198 self ._device_config = device_config
198199 self ._cached_config = {}
199200 self .is_multi_npu = device_info .is_multi_npu ()
201+ self ._device_running_config = device_info .get_device_runtime_metadata ()
200202
201203 def handler (self , feature_name , op , feature_cfg ):
202204 if not feature_cfg :
@@ -205,7 +207,7 @@ class FeatureHandler(object):
205207 self ._feature_state_table ._del (feature_name )
206208 return
207209
208- feature = Feature (feature_name , feature_cfg , self ._device_config )
210+ feature = Feature (feature_name , feature_cfg , self ._device_config | self . _device_running_config )
209211 self ._cached_config .setdefault (feature_name , Feature (feature_name , {}))
210212
211213 # Change auto-restart configuration first.
@@ -230,14 +232,14 @@ class FeatureHandler(object):
230232 """
231233 Summary:
232234 Updates the state field in the FEATURE|* tables as the state field
233- might have to be rendered based on DEVICE_METADATA table
235+ might have to be rendered based on DEVICE_METADATA table and generated Device Running Metadata
234236 """
235237 for feature_name in feature_table .keys ():
236238 if not feature_name :
237239 syslog .syslog (syslog .LOG_WARNING , "Feature is None" )
238240 continue
239241
240- feature = Feature (feature_name , feature_table [feature_name ], self ._device_config )
242+ feature = Feature (feature_name , feature_table [feature_name ], self ._device_config | self . _device_running_config )
241243
242244 self ._cached_config .setdefault (feature_name , feature )
243245 self .update_systemd_config (feature )
@@ -283,8 +285,50 @@ class FeatureHandler(object):
283285 self .disable_feature (feature )
284286 syslog .syslog (syslog .LOG_INFO , "Feature {} is stopped and disabled" .format (feature .name ))
285287
288+ if self .is_multi_npu :
289+ self .sync_feature_asic_scope (feature )
290+
286291 return True
287292
293+ def sync_feature_asic_scope (self , feature_config ):
294+ """Updates the has_per_asic_scope field in the FEATURE|* tables as the field
295+ might have to be rendered based on DEVICE_METADATA table or Device Running configuration.
296+ Disable the ASIC instance service unit file it the render value is False and update config
297+
298+ Args:
299+ feature: An object represents a feature's configuration in `FEATURE`
300+ table of `CONFIG_DB`.
301+
302+ Returns:
303+ None.
304+ """
305+
306+ cmds = []
307+ feature_names , feature_suffixes = self .get_multiasic_feature_instances (feature_config , True )
308+ for feature_name in feature_names :
309+ if '@' not in feature_name :
310+ continue
311+ unit_file_state = self .get_systemd_unit_state ("{}.{}" .format (feature_name , feature_suffixes [- 1 ]))
312+ if not unit_file_state :
313+ continue
314+ if unit_file_state == "enabled" and not feature_config .has_per_asic_scope :
315+ for suffix in reversed (feature_suffixes ):
316+ cmds .append ("sudo systemctl stop {}.{}" .format (feature_name , suffix ))
317+ cmds .append ("sudo systemctl disable {}.{}" .format (feature_name , feature_suffixes [- 1 ]))
318+ cmds .append ("sudo systemctl mask {}.{}" .format (feature_name , feature_suffixes [- 1 ]))
319+ for cmd in cmds :
320+ syslog .syslog (syslog .LOG_INFO , "Running cmd: '{}'" .format (cmd ))
321+ try :
322+ run_cmd (cmd , raise_exception = True )
323+ except Exception as err :
324+ syslog .syslog (syslog .LOG_ERR , "Feature '{}.{}' failed to be stopped and disabled"
325+ .format (feature .name , feature_suffixes [- 1 ]))
326+ self .set_feature_state (feature , self .FEATURE_STATE_FAILED )
327+ return
328+ self ._config_db .mod_entry ('FEATURE' , feature_config .name , {'has_per_asic_scope' : feature_config .has_per_asic_scope })
329+
330+
331+
288332 def update_systemd_config (self , feature_config ):
289333 """Updates `Restart=` field in feature's systemd configuration file
290334 according to the value of `auto_restart` field in `FEATURE` table of `CONFIG_DB`.
@@ -323,12 +367,12 @@ class FeatureHandler(object):
323367 except Exception as err :
324368 syslog .syslog (syslog .LOG_ERR , "Failed to reload systemd configuration files!" )
325369
326- def get_multiasic_feature_instances (self , feature ):
370+ def get_multiasic_feature_instances (self , feature , all_instance = False ):
327371 # Create feature name suffix depending feature is running in host or namespace or in both
328372 feature_names = (
329373 ([feature .name ] if feature .has_global_scope or not self .is_multi_npu else []) +
330374 ([(feature .name + '@' + str (asic_inst )) for asic_inst in range (device_info .get_num_npus ())
331- if feature . has_per_asic_scope and self . is_multi_npu ])
375+ if self . is_multi_npu and ( all_instance or feature . has_per_asic_scope ) ])
332376 )
333377
334378 if not feature_names :
@@ -358,7 +402,7 @@ class FeatureHandler(object):
358402 for feature_name in feature_names :
359403 # Check if it is already enabled, if yes skip the system call
360404 unit_file_state = self .get_systemd_unit_state ("{}.{}" .format (feature_name , feature_suffixes [- 1 ]))
361- if unit_file_state == "enabled" :
405+ if unit_file_state == "enabled" or not unit_file_state :
362406 continue
363407
364408 for suffix in feature_suffixes :
@@ -388,7 +432,7 @@ class FeatureHandler(object):
388432 for feature_name in feature_names :
389433 # Check if it is already disabled, if yes skip the system call
390434 unit_file_state = self .get_systemd_unit_state ("{}.{}" .format (feature_name , feature_suffixes [- 1 ]))
391- if unit_file_state in ("disabled" , "masked" ):
435+ if unit_file_state in ("disabled" , "masked" ) or not unit_file_state :
392436 continue
393437
394438 for suffix in reversed (feature_suffixes ):
0 commit comments