@@ -146,30 +146,31 @@ class Feature(object):
146
146
"""
147
147
148
148
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' ] )
150
150
self .auto_restart = feature_cfg .get ('auto_restart' , 'disabled' )
151
151
self .has_timer = safe_eval (feature_cfg .get ('has_timer' , 'False' ))
152
152
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' ] ))
154
154
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.
157
157
158
158
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
161
162
Returns:
162
- (str): Target feature state
163
+ (str): Target feature table value for given key
163
164
"""
164
165
165
- if state_configuration is None :
166
+ if configuration is None :
166
167
return None
167
168
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
173
174
174
175
def compare_state (self , feature_name , feature_cfg ):
175
176
if self .name != feature_name or not isinstance (feature_cfg , dict ):
@@ -197,6 +198,7 @@ class FeatureHandler(object):
197
198
self ._device_config = device_config
198
199
self ._cached_config = {}
199
200
self .is_multi_npu = device_info .is_multi_npu ()
201
+ self ._device_running_config = device_info .get_device_runtime_metadata ()
200
202
201
203
def handler (self , feature_name , op , feature_cfg ):
202
204
if not feature_cfg :
@@ -205,7 +207,7 @@ class FeatureHandler(object):
205
207
self ._feature_state_table ._del (feature_name )
206
208
return
207
209
208
- feature = Feature (feature_name , feature_cfg , self ._device_config )
210
+ feature = Feature (feature_name , feature_cfg , self ._device_config | self . _device_running_config )
209
211
self ._cached_config .setdefault (feature_name , Feature (feature_name , {}))
210
212
211
213
# Change auto-restart configuration first.
@@ -230,14 +232,14 @@ class FeatureHandler(object):
230
232
"""
231
233
Summary:
232
234
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
234
236
"""
235
237
for feature_name in feature_table .keys ():
236
238
if not feature_name :
237
239
syslog .syslog (syslog .LOG_WARNING , "Feature is None" )
238
240
continue
239
241
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 )
241
243
242
244
self ._cached_config .setdefault (feature_name , feature )
243
245
self .update_systemd_config (feature )
@@ -283,8 +285,50 @@ class FeatureHandler(object):
283
285
self .disable_feature (feature )
284
286
syslog .syslog (syslog .LOG_INFO , "Feature {} is stopped and disabled" .format (feature .name ))
285
287
288
+ if self .is_multi_npu :
289
+ self .sync_feature_asic_scope (feature )
290
+
286
291
return True
287
292
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
+
288
332
def update_systemd_config (self , feature_config ):
289
333
"""Updates `Restart=` field in feature's systemd configuration file
290
334
according to the value of `auto_restart` field in `FEATURE` table of `CONFIG_DB`.
@@ -323,12 +367,12 @@ class FeatureHandler(object):
323
367
except Exception as err :
324
368
syslog .syslog (syslog .LOG_ERR , "Failed to reload systemd configuration files!" )
325
369
326
- def get_multiasic_feature_instances (self , feature ):
370
+ def get_multiasic_feature_instances (self , feature , all_instance = False ):
327
371
# Create feature name suffix depending feature is running in host or namespace or in both
328
372
feature_names = (
329
373
([feature .name ] if feature .has_global_scope or not self .is_multi_npu else []) +
330
374
([(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 ) ])
332
376
)
333
377
334
378
if not feature_names :
@@ -358,7 +402,7 @@ class FeatureHandler(object):
358
402
for feature_name in feature_names :
359
403
# Check if it is already enabled, if yes skip the system call
360
404
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 :
362
406
continue
363
407
364
408
for suffix in feature_suffixes :
@@ -388,7 +432,7 @@ class FeatureHandler(object):
388
432
for feature_name in feature_names :
389
433
# Check if it is already disabled, if yes skip the system call
390
434
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 :
392
436
continue
393
437
394
438
for suffix in reversed (feature_suffixes ):
0 commit comments