Skip to content

Commit ada603c

Browse files
authored
[config]Support multi-asic Golden Config override (sonic-net#2738)
#### What I did Support multi-asic Golden Config Override #### How I did it Add ConfigMgmt support for ASIC validation. Modify override config cli to support multi-asic. #### How to verify it Unit test: tests/config_override_test.py::TestConfigOverrideMultiasic::test_macsec_override PASSED [ 8%] tests/config_override_test.py::TestConfigOverrideMultiasic::test_device_metadata_table_rm PASSED [ 8%]
1 parent 88a7daa commit ada603c

File tree

5 files changed

+149
-32
lines changed

5 files changed

+149
-32
lines changed

config/config_mgmt.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ class ConfigMgmt():
3535
to verify config for the commands which are capable of change in config DB.
3636
'''
3737

38-
def __init__(self, source="configDB", debug=False, allowTablesWithoutYang=True, sonicYangOptions=0):
38+
def __init__(self, source="configDB", debug=False, allowTablesWithoutYang=True,
39+
sonicYangOptions=0, configdb=None):
3940
'''
4041
Initialise the class, --read the config, --load in data tree.
4142
@@ -44,6 +45,7 @@ def __init__(self, source="configDB", debug=False, allowTablesWithoutYang=True,
4445
debug (bool): verbose mode.
4546
allowTablesWithoutYang (bool): allow tables without yang model in
4647
config or not.
48+
configdb: configdb to work on.
4749
4850
Returns:
4951
void
@@ -54,6 +56,11 @@ def __init__(self, source="configDB", debug=False, allowTablesWithoutYang=True,
5456
self.source = source
5557
self.allowTablesWithoutYang = allowTablesWithoutYang
5658
self.sonicYangOptions = sonicYangOptions
59+
if configdb is None:
60+
self.configdb = ConfigDBConnector()
61+
self.configdb.connect()
62+
else:
63+
self.configdb = configdb
5764

5865
# logging vars
5966
self.SYSLOG_IDENTIFIER = "ConfigMgmt"
@@ -194,8 +201,7 @@ def readConfigDB(self):
194201
self.sysLog(doPrint=True, msg='Reading data from Redis configDb')
195202
# Read from config DB on sonic switch
196203
data = dict()
197-
configdb = ConfigDBConnector()
198-
configdb.connect()
204+
configdb = self.configdb
199205
sonic_cfggen.deep_update(data, sonic_cfggen.FormatConverter.db_to_output(configdb.get_config()))
200206
self.configdbJsonIn = sonic_cfggen.FormatConverter.to_serialized(data)
201207
self.sysLog(syslog.LOG_DEBUG, 'Reading Input from ConfigDB {}'.\
@@ -215,8 +221,7 @@ def writeConfigDB(self, jDiff):
215221
'''
216222
self.sysLog(doPrint=True, msg='Writing in Config DB')
217223
data = dict()
218-
configdb = ConfigDBConnector()
219-
configdb.connect(False)
224+
configdb = self.configdb
220225
sonic_cfggen.deep_update(data, sonic_cfggen.FormatConverter.to_deserialized(jDiff))
221226
self.sysLog(msg="Write in DB: {}".format(data))
222227
configdb.mod_config(sonic_cfggen.FormatConverter.output_to_db(data))

config/main.py

+30-25
Original file line numberDiff line numberDiff line change
@@ -1825,36 +1825,41 @@ def override_config_table(db, input_config_db, dry_run):
18251825
fg='magenta')
18261826
sys.exit(1)
18271827

1828-
config_db = db.cfgdb
1828+
cfgdb_clients = db.cfgdb_clients
18291829

1830-
# Read config from configDB
1831-
current_config = config_db.get_config()
1832-
# Serialize to the same format as json input
1833-
sonic_cfggen.FormatConverter.to_serialized(current_config)
1830+
for ns, config_db in cfgdb_clients.items():
1831+
# Read config from configDB
1832+
current_config = config_db.get_config()
1833+
# Serialize to the same format as json input
1834+
sonic_cfggen.FormatConverter.to_serialized(current_config)
18341835

1835-
updated_config = update_config(current_config, config_input)
1836+
if multi_asic.is_multi_asic():
1837+
ns_config_input = config_input[ns]
1838+
else:
1839+
ns_config_input = config_input
1840+
updated_config = update_config(current_config, ns_config_input)
18361841

1837-
yang_enabled = device_info.is_yang_config_validation_enabled(config_db)
1838-
if yang_enabled:
1839-
# The ConfigMgmt will load YANG and running
1840-
# config during initialization.
1841-
try:
1842-
cm = ConfigMgmt()
1843-
cm.validateConfigData()
1844-
except Exception as ex:
1845-
click.secho("Failed to validate running config. Error: {}".format(ex), fg="magenta")
1846-
sys.exit(1)
1842+
yang_enabled = device_info.is_yang_config_validation_enabled(config_db)
1843+
if yang_enabled:
1844+
# The ConfigMgmt will load YANG and running
1845+
# config during initialization.
1846+
try:
1847+
cm = ConfigMgmt(configdb=config_db)
1848+
cm.validateConfigData()
1849+
except Exception as ex:
1850+
click.secho("Failed to validate running config. Error: {}".format(ex), fg="magenta")
1851+
sys.exit(1)
18471852

1848-
# Validate input config
1849-
validate_config_by_cm(cm, config_input, "config_input")
1850-
# Validate updated whole config
1851-
validate_config_by_cm(cm, updated_config, "updated_config")
1853+
# Validate input config
1854+
validate_config_by_cm(cm, ns_config_input, "config_input")
1855+
# Validate updated whole config
1856+
validate_config_by_cm(cm, updated_config, "updated_config")
18521857

1853-
if dry_run:
1854-
print(json.dumps(updated_config, sort_keys=True,
1855-
indent=4, cls=minigraph_encoder))
1856-
else:
1857-
override_config_db(config_db, config_input)
1858+
if dry_run:
1859+
print(json.dumps(updated_config, sort_keys=True,
1860+
indent=4, cls=minigraph_encoder))
1861+
else:
1862+
override_config_db(config_db, ns_config_input)
18581863

18591864

18601865
def validate_config_by_cm(cm, config_json, jname):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"": {
3+
"DEVICE_METADATA": {}
4+
},
5+
"asic0": {
6+
"DEVICE_METADATA": {}
7+
},
8+
"asic1": {
9+
"DEVICE_METADATA": {}
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"": {
3+
"MACSEC_PROFILE": {
4+
"profile": {
5+
"key": "value"
6+
}
7+
}
8+
},
9+
"asic0": {
10+
"MACSEC_PROFILE": {
11+
"profile": {
12+
"key": "value"
13+
}
14+
}
15+
},
16+
"asic1": {
17+
"MACSEC_PROFILE": {
18+
"profile": {
19+
"key": "value"
20+
}
21+
}
22+
}
23+
}

tests/config_override_test.py

+75-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import json
33
import filecmp
4+
import importlib
45
import config.main as config
56

67
from click.testing import CliRunner
@@ -20,6 +21,8 @@
2021
RUNNING_CONFIG_YANG_FAILURE = os.path.join(DATA_DIR, "running_config_yang_failure.json")
2122
GOLDEN_INPUT_YANG_FAILURE = os.path.join(DATA_DIR, "golden_input_yang_failure.json")
2223
FINAL_CONFIG_YANG_FAILURE = os.path.join(DATA_DIR, "final_config_yang_failure.json")
24+
MULTI_ASIC_MACSEC_OV = os.path.join(DATA_DIR, "multi_asic_macsec_ov.json")
25+
MULTI_ASIC_DEVICE_METADATA_RM = os.path.join(DATA_DIR, "multi_asic_dm_rm.json")
2326

2427
# Load sonic-cfggen from source since /usr/local/bin/sonic-cfggen does not have .py extension.
2528
sonic_cfggen = load_module_from_source('sonic_cfggen', '/usr/local/bin/sonic-cfggen')
@@ -173,7 +176,7 @@ def test_yang_verification_enabled(self):
173176
def is_yang_config_validation_enabled_side_effect(filename):
174177
return True
175178

176-
def config_mgmt_side_effect():
179+
def config_mgmt_side_effect(configdb):
177180
return config_mgmt.ConfigMgmt(source=CONFIG_DB_JSON_FILE)
178181

179182
db = Db()
@@ -232,7 +235,7 @@ def check_yang_verification_failure(self, db, config, running_config,
232235
def read_json_file_side_effect(filename):
233236
return golden_config
234237

235-
def config_mgmt_side_effect():
238+
def config_mgmt_side_effect(configdb):
236239
return config_mgmt.ConfigMgmt(source=CONFIG_DB_JSON_FILE)
237240

238241
# ConfigMgmt will call ConfigDBConnector to load default config_db.json.
@@ -257,3 +260,73 @@ def teardown_class(cls):
257260
print("TEARDOWN")
258261
os.environ["UTILITIES_UNIT_TESTING"] = "0"
259262
return
263+
264+
265+
class TestConfigOverrideMultiasic(object):
266+
@classmethod
267+
def setup_class(cls):
268+
print("SETUP")
269+
os.environ["UTILITIES_UNIT_TESTING"] = "1"
270+
os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "multi_asic"
271+
# change to multi asic config
272+
from .mock_tables import dbconnector
273+
from .mock_tables import mock_multi_asic
274+
importlib.reload(mock_multi_asic)
275+
dbconnector.load_namespace_config()
276+
return
277+
278+
def test_macsec_override(self):
279+
def read_json_file_side_effect(filename):
280+
with open(MULTI_ASIC_MACSEC_OV, "r") as f:
281+
macsec_profile = json.load(f)
282+
return macsec_profile
283+
db = Db()
284+
cfgdb_clients = db.cfgdb_clients
285+
286+
# The profile_content was copied from MULTI_ASIC_MACSEC_OV, where all
287+
# ns sharing the same content: {"profile": {"key": "value"}}
288+
profile_content = {"profile": {"key": "value"}}
289+
290+
with mock.patch('config.main.read_json_file',
291+
mock.MagicMock(side_effect=read_json_file_side_effect)):
292+
runner = CliRunner()
293+
result = runner.invoke(config.config.commands["override-config-table"],
294+
['golden_config_db.json'], obj=db)
295+
assert result.exit_code == 0
296+
297+
for ns, config_db in cfgdb_clients.items():
298+
assert config_db.get_config()['MACSEC_PROFILE'] == profile_content
299+
300+
def test_device_metadata_table_rm(self):
301+
def read_json_file_side_effect(filename):
302+
with open(MULTI_ASIC_DEVICE_METADATA_RM, "r") as f:
303+
device_metadata = json.load(f)
304+
return device_metadata
305+
db = Db()
306+
cfgdb_clients = db.cfgdb_clients
307+
308+
for ns, config_db in cfgdb_clients.items():
309+
assert 'DEVICE_METADATA' in config_db.get_config()
310+
311+
with mock.patch('config.main.read_json_file',
312+
mock.MagicMock(side_effect=read_json_file_side_effect)):
313+
runner = CliRunner()
314+
result = runner.invoke(config.config.commands["override-config-table"],
315+
['golden_config_db.json'], obj=db)
316+
assert result.exit_code == 0
317+
318+
for ns, config_db in cfgdb_clients.items():
319+
assert 'DEVICE_METADATA' not in config_db.get_config()
320+
321+
322+
@classmethod
323+
def teardown_class(cls):
324+
print("TEARDOWN")
325+
os.environ["UTILITIES_UNIT_TESTING"] = "0"
326+
os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = ""
327+
# change back to single asic config
328+
from .mock_tables import dbconnector
329+
from .mock_tables import mock_single_asic
330+
importlib.reload(mock_single_asic)
331+
dbconnector.load_namespace_config()
332+
return

0 commit comments

Comments
 (0)