Skip to content

Commit 3a9995b

Browse files
authored
[config]Support multi-asic Golden Config override with fix (sonic-net#2825)
ADO: 17746282 #### What I did Support multi-asic Golden Config Override with fix based on sonic-net/sonic-utilities#2738 #### 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 3fb3258 commit 3a9995b

File tree

5 files changed

+159
-34
lines changed

5 files changed

+159
-34
lines changed

config/config_mgmt.py

+14-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,7 @@ def __init__(self, source="configDB", debug=False, allowTablesWithoutYang=True,
5456
self.source = source
5557
self.allowTablesWithoutYang = allowTablesWithoutYang
5658
self.sonicYangOptions = sonicYangOptions
59+
self.configdb = configdb
5760

5861
# logging vars
5962
self.SYSLOG_IDENTIFIER = "ConfigMgmt"
@@ -194,8 +197,11 @@ def readConfigDB(self):
194197
self.sysLog(doPrint=True, msg='Reading data from Redis configDb')
195198
# Read from config DB on sonic switch
196199
data = dict()
197-
configdb = ConfigDBConnector()
198-
configdb.connect()
200+
if self.configdb is None:
201+
configdb = ConfigDBConnector()
202+
configdb.connect()
203+
else:
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,11 @@ 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+
if self.configdb is None:
225+
configdb = ConfigDBConnector()
226+
configdb.connect(False)
227+
else:
228+
configdb = self.configdb
220229
sonic_cfggen.deep_update(data, sonic_cfggen.FormatConverter.to_deserialized(jDiff))
221230
self.sysLog(msg="Write in DB: {}".format(data))
222231
configdb.mod_config(sonic_cfggen.FormatConverter.output_to_db(data))

config/main.py

+36-27
Original file line numberDiff line numberDiff line change
@@ -1849,36 +1849,45 @@ def override_config_table(db, input_config_db, dry_run):
18491849
fg='magenta')
18501850
sys.exit(1)
18511851

1852-
config_db = db.cfgdb
1853-
1854-
# Read config from configDB
1855-
current_config = config_db.get_config()
1856-
# Serialize to the same format as json input
1857-
sonic_cfggen.FormatConverter.to_serialized(current_config)
1858-
1859-
updated_config = update_config(current_config, config_input)
1852+
cfgdb_clients = db.cfgdb_clients
1853+
1854+
for ns, config_db in cfgdb_clients.items():
1855+
# Read config from configDB
1856+
current_config = config_db.get_config()
1857+
# Serialize to the same format as json input
1858+
sonic_cfggen.FormatConverter.to_serialized(current_config)
1859+
1860+
if multi_asic.is_multi_asic():
1861+
# Golden Config will use "localhost" to represent host name
1862+
if ns == DEFAULT_NAMESPACE:
1863+
ns_config_input = config_input["localhost"]
1864+
else:
1865+
ns_config_input = config_input[ns]
1866+
else:
1867+
ns_config_input = config_input
1868+
updated_config = update_config(current_config, ns_config_input)
18601869

1861-
yang_enabled = device_info.is_yang_config_validation_enabled(config_db)
1862-
if yang_enabled:
1863-
# The ConfigMgmt will load YANG and running
1864-
# config during initialization.
1865-
try:
1866-
cm = ConfigMgmt()
1867-
cm.validateConfigData()
1868-
except Exception as ex:
1869-
click.secho("Failed to validate running config. Error: {}".format(ex), fg="magenta")
1870-
sys.exit(1)
1870+
yang_enabled = device_info.is_yang_config_validation_enabled(config_db)
1871+
if yang_enabled:
1872+
# The ConfigMgmt will load YANG and running
1873+
# config during initialization.
1874+
try:
1875+
cm = ConfigMgmt(configdb=config_db)
1876+
cm.validateConfigData()
1877+
except Exception as ex:
1878+
click.secho("Failed to validate running config. Error: {}".format(ex), fg="magenta")
1879+
sys.exit(1)
18711880

1872-
# Validate input config
1873-
validate_config_by_cm(cm, config_input, "config_input")
1874-
# Validate updated whole config
1875-
validate_config_by_cm(cm, updated_config, "updated_config")
1881+
# Validate input config
1882+
validate_config_by_cm(cm, ns_config_input, "config_input")
1883+
# Validate updated whole config
1884+
validate_config_by_cm(cm, updated_config, "updated_config")
18761885

1877-
if dry_run:
1878-
print(json.dumps(updated_config, sort_keys=True,
1879-
indent=4, cls=minigraph_encoder))
1880-
else:
1881-
override_config_db(config_db, config_input)
1886+
if dry_run:
1887+
print(json.dumps(updated_config, sort_keys=True,
1888+
indent=4, cls=minigraph_encoder))
1889+
else:
1890+
override_config_db(config_db, ns_config_input)
18821891

18831892

18841893
def validate_config_by_cm(cm, config_json, jname):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"localhost": {
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+
"localhost": {
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)