Skip to content

Commit 7a06457

Browse files
authored
[auto_ts] Enable register/de-register auto_ts config for APP Extension (sonic-net#2139)
- What I did Added the ability to dynamically add entries into AUTO_TECHSUPPORT_FEATURE when a new app extension is installed/updated More details can be found here: sonic-net/SONiC#990 - How I did it Enhanced FeatureRegistry Class to also support AUTO_TECHSUPPORT_FEATURE table - How to verify it Unit Tests verify on live switch Signed-off-by: Vivek Reddy Karri <[email protected]>
1 parent 083ebcc commit 7a06457

File tree

2 files changed

+169
-3
lines changed

2 files changed

+169
-3
lines changed

sonic_package_manager/service_creator/feature.py

+62-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
#!/usr/bin/env python
22

33
""" This module implements new feature registration/de-registration in SONiC system. """
4-
4+
import copy
55
from typing import Dict, Type
66

7+
from sonic_package_manager.logger import log
78
from sonic_package_manager.manifest import Manifest
89
from sonic_package_manager.service_creator.sonic_db import SonicDB
910

@@ -15,6 +16,14 @@
1516
'set_owner': 'local'
1617
}
1718

19+
AUTO_TS_GLOBAL = "AUTO_TECHSUPPORT"
20+
AUTO_TS_FEATURE = "AUTO_TECHSUPPORT_FEATURE"
21+
CFG_STATE = "state"
22+
# TODO: Enable available_mem_threshold once the mem_leak_auto_ts feature is available
23+
DEFAULT_AUTO_TS_FEATURE_CONFIG = {
24+
'state': 'disabled',
25+
'rate_limit_interval': '600'
26+
}
1827

1928
def is_enabled(cfg):
2029
return cfg.get('state', 'disabled').lower() == 'enabled'
@@ -25,8 +34,11 @@ def is_multi_instance(cfg):
2534

2635

2736
class FeatureRegistry:
28-
""" FeatureRegistry class provides an interface to
29-
register/de-register new feature persistently. """
37+
""" 1) FeatureRegistry class provides an interface to
38+
register/de-register new feature tables persistently.
39+
2) Writes persistent configuration to FEATURE &
40+
AUTO_TECHSUPPORT_FEATURE tables
41+
"""
3042

3143
def __init__(self, sonic_db: Type[SonicDB]):
3244
self._sonic_db = sonic_db
@@ -60,6 +72,9 @@ def register(self,
6072
new_cfg = {**new_cfg, **non_cfg_entries}
6173

6274
conn.set_entry(FEATURE, name, new_cfg)
75+
76+
if self.register_auto_ts(name):
77+
log.info(f'{name} entry is added to {AUTO_TS_FEATURE} table')
6378

6479
def deregister(self, name: str):
6580
""" Deregister feature by name.
@@ -73,6 +88,7 @@ def deregister(self, name: str):
7388
db_connetors = self._sonic_db.get_connectors()
7489
for conn in db_connetors:
7590
conn.set_entry(FEATURE, name, None)
91+
conn.set_entry(AUTO_TS_FEATURE, name, None)
7692

7793
def update(self,
7894
old_manifest: Manifest,
@@ -103,6 +119,9 @@ def update(self,
103119
new_cfg = {**new_cfg, **non_cfg_entries}
104120

105121
conn.set_entry(FEATURE, new_name, new_cfg)
122+
123+
if self.register_auto_ts(new_name, old_name):
124+
log.info(f'{new_name} entry is added to {AUTO_TS_FEATURE} table')
106125

107126
def is_feature_enabled(self, name: str) -> bool:
108127
""" Returns whether the feature is current enabled
@@ -123,6 +142,46 @@ def get_multi_instance_features(self):
123142
features = conn.get_table(FEATURE)
124143
return [feature for feature, cfg in features.items() if is_multi_instance(cfg)]
125144

145+
def infer_auto_ts_capability(self, init_cfg_conn):
146+
""" Determine whether to enable/disable the state for new feature
147+
AUTO_TS provides a compile-time knob to enable/disable this feature
148+
Default State for the new feature follows the decision made at compile time.
149+
150+
Args:
151+
init_cfg_conn: PersistentConfigDbConnector for init_cfg.json
152+
Returns:
153+
Capability: Tuple: (bool, ["enabled", "disabled"])
154+
"""
155+
cfg = init_cfg_conn.get_entry(AUTO_TS_GLOBAL, "GLOBAL")
156+
default_state = cfg.get(CFG_STATE, "")
157+
if not default_state:
158+
return (False, "disabled")
159+
else:
160+
return (True, default_state)
161+
162+
def register_auto_ts(self, new_name, old_name=None):
163+
""" Registers auto_ts feature
164+
"""
165+
# Infer and update default config
166+
init_cfg_conn = self._sonic_db.get_initial_db_connector()
167+
def_cfg = DEFAULT_AUTO_TS_FEATURE_CONFIG.copy()
168+
(auto_ts_add_cfg, auto_ts_state) = self.infer_auto_ts_capability(init_cfg_conn)
169+
def_cfg['state'] = auto_ts_state
170+
171+
if not auto_ts_add_cfg:
172+
log.debug("Skip adding AUTO_TECHSUPPORT_FEATURE table because no AUTO_TECHSUPPORT|GLOBAL entry is found")
173+
return False
174+
175+
for conn in self._sonic_db.get_connectors():
176+
new_cfg = copy.deepcopy(def_cfg)
177+
if old_name:
178+
current_cfg = conn.get_entry(AUTO_TS_FEATURE, old_name)
179+
conn.set_entry(AUTO_TS_FEATURE, old_name, None)
180+
new_cfg.update(current_cfg)
181+
182+
conn.set_entry(AUTO_TS_FEATURE, new_name, new_cfg)
183+
return True
184+
126185
@staticmethod
127186
def get_default_feature_entries(state=None, owner=None) -> Dict[str, str]:
128187
""" Get configurable feature table entries:

tests/sonic_package_manager/test_service_creator.py

+107
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ def test_feature_registration(mock_sonic_db, manifest):
205205
mock_connector = Mock()
206206
mock_connector.get_entry = Mock(return_value={})
207207
mock_sonic_db.get_connectors = Mock(return_value=[mock_connector])
208+
mock_sonic_db.get_initial_db_connector = Mock(return_value=mock_connector)
208209
feature_registry = FeatureRegistry(mock_sonic_db)
209210
feature_registry.register(manifest)
210211
mock_connector.set_entry.assert_called_with('FEATURE', 'test', {
@@ -258,6 +259,7 @@ def test_feature_registration_with_timer(mock_sonic_db, manifest):
258259
mock_connector = Mock()
259260
mock_connector.get_entry = Mock(return_value={})
260261
mock_sonic_db.get_connectors = Mock(return_value=[mock_connector])
262+
mock_sonic_db.get_initial_db_connector = Mock(return_value=mock_connector)
261263
feature_registry = FeatureRegistry(mock_sonic_db)
262264
feature_registry.register(manifest)
263265
mock_connector.set_entry.assert_called_with('FEATURE', 'test', {
@@ -275,6 +277,7 @@ def test_feature_registration_with_non_default_owner(mock_sonic_db, manifest):
275277
mock_connector = Mock()
276278
mock_connector.get_entry = Mock(return_value={})
277279
mock_sonic_db.get_connectors = Mock(return_value=[mock_connector])
280+
mock_sonic_db.get_initial_db_connector = Mock(return_value=mock_connector)
278281
feature_registry = FeatureRegistry(mock_sonic_db)
279282
feature_registry.register(manifest, owner='kube')
280283
mock_connector.set_entry.assert_called_with('FEATURE', 'test', {
@@ -286,3 +289,107 @@ def test_feature_registration_with_non_default_owner(mock_sonic_db, manifest):
286289
'has_global_scope': 'True',
287290
'has_timer': 'False',
288291
})
292+
293+
294+
class AutoTSHelp:
295+
""" Helper class for Auto TS Feature Registry Tests
296+
"""
297+
GLOBAL_STATE = {}
298+
299+
@classmethod
300+
def get_entry(cls, table, key):
301+
if table == "AUTO_TECHSUPPORT" and key == "GLOBAL":
302+
return AutoTSHelp.GLOBAL_STATE
303+
elif table == "AUTO_TECHSUPPORT_FEATURE" and key == "test":
304+
return {"state" : "enabled", "rate_limit_interval" : "600"}
305+
else:
306+
return {}
307+
308+
@classmethod
309+
def get_entry_running_cfg(cls, table, key):
310+
if table == "AUTO_TECHSUPPORT_FEATURE" and key == "test":
311+
return {"state" : "disabled", "rate_limit_interval" : "1000"}
312+
else:
313+
return {}
314+
315+
316+
def test_auto_ts_global_disabled(mock_sonic_db, manifest):
317+
mock_init_cfg = Mock()
318+
AutoTSHelp.GLOBAL_STATE = {"state" : "disabled"}
319+
mock_init_cfg.get_entry = Mock(side_effect=AutoTSHelp.get_entry)
320+
mock_sonic_db.get_connectors = Mock(return_value=[mock_init_cfg])
321+
mock_sonic_db.get_initial_db_connector = Mock(return_value=mock_init_cfg)
322+
feature_registry = FeatureRegistry(mock_sonic_db)
323+
feature_registry.register(manifest)
324+
mock_init_cfg.set_entry.assert_any_call("AUTO_TECHSUPPORT_FEATURE", "test", {
325+
"state" : "disabled",
326+
"rate_limit_interval" : "600"
327+
}
328+
)
329+
330+
331+
def test_auto_ts_global_enabled(mock_sonic_db, manifest):
332+
mock_init_cfg = Mock()
333+
AutoTSHelp.GLOBAL_STATE = {"state" : "enabled"}
334+
mock_init_cfg.get_entry = Mock(side_effect=AutoTSHelp.get_entry)
335+
mock_sonic_db.get_connectors = Mock(return_value=[mock_init_cfg])
336+
mock_sonic_db.get_initial_db_connector = Mock(return_value=mock_init_cfg)
337+
feature_registry = FeatureRegistry(mock_sonic_db)
338+
feature_registry.register(manifest)
339+
mock_init_cfg.set_entry.assert_any_call("AUTO_TECHSUPPORT_FEATURE", "test", {
340+
"state" : "enabled",
341+
"rate_limit_interval" : "600"
342+
}
343+
)
344+
345+
346+
def test_auto_ts_deregister(mock_sonic_db):
347+
mock_connector = Mock()
348+
mock_sonic_db.get_connectors = Mock(return_value=[mock_connector])
349+
feature_registry = FeatureRegistry(mock_sonic_db)
350+
feature_registry.deregister("test")
351+
mock_connector.set_entry.assert_any_call("AUTO_TECHSUPPORT_FEATURE", "test", None)
352+
353+
354+
def test_auto_ts_feature_update_flow(mock_sonic_db, manifest):
355+
new_manifest = copy.deepcopy(manifest)
356+
new_manifest['service']['name'] = 'test_new'
357+
new_manifest['service']['delayed'] = True
358+
359+
AutoTSHelp.GLOBAL_STATE = {"state" : "enabled"}
360+
# Mock init_cfg connector
361+
mock_init_cfg = Mock()
362+
mock_init_cfg.get_entry = Mock(side_effect=AutoTSHelp.get_entry)
363+
364+
# Mock running/peristent cfg connector
365+
mock_other_cfg = Mock()
366+
mock_other_cfg.get_entry = Mock(side_effect=AutoTSHelp.get_entry_running_cfg)
367+
368+
# Setup sonic_db class
369+
mock_sonic_db.get_connectors = Mock(return_value=[mock_init_cfg, mock_other_cfg])
370+
mock_sonic_db.get_initial_db_connector = Mock(return_value=mock_init_cfg)
371+
372+
feature_registry = FeatureRegistry(mock_sonic_db)
373+
feature_registry.update(manifest, new_manifest)
374+
375+
mock_init_cfg.set_entry.assert_has_calls(
376+
[
377+
call("AUTO_TECHSUPPORT_FEATURE", "test", None),
378+
call("AUTO_TECHSUPPORT_FEATURE", "test_new", {
379+
"state" : "enabled",
380+
"rate_limit_interval" : "600"
381+
})
382+
],
383+
any_order = True
384+
)
385+
386+
mock_other_cfg.set_entry.assert_has_calls(
387+
[
388+
call("AUTO_TECHSUPPORT_FEATURE", "test", None),
389+
call("AUTO_TECHSUPPORT_FEATURE", "test_new", {
390+
"state" : "disabled",
391+
"rate_limit_interval" : "1000"
392+
})
393+
],
394+
any_order = True
395+
)

0 commit comments

Comments
 (0)