Skip to content

Commit 75ca81e

Browse files
authored
[202205][config]Support multi-asic Golden Config override with fix (#2825) (#2862)
ADO: 17746282 Support multi-asic Golden Config Override with fix based on #2738 Add ConfigMgmt support for ASIC validation. Modify override config cli to support multi-asic. 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 ec47214 commit 75ca81e

File tree

5 files changed

+159
-34
lines changed

5 files changed

+159
-34
lines changed

config/config_mgmt.py

Lines changed: 14 additions & 5 deletions
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):
38+
def __init__(self, source="configDB", debug=False, allowTablesWithoutYang=True,
39+
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
@@ -53,6 +55,7 @@ def __init__(self, source="configDB", debug=False, allowTablesWithoutYang=True):
5355
self.configdbJsonOut = None
5456
self.source = source
5557
self.allowTablesWithoutYang = allowTablesWithoutYang
58+
self.configdb = configdb
5659

5760
# logging vars
5861
self.SYSLOG_IDENTIFIER = "ConfigMgmt"
@@ -193,8 +196,11 @@ def readConfigDB(self):
193196
self.sysLog(doPrint=True, msg='Reading data from Redis configDb')
194197
# Read from config DB on sonic switch
195198
data = dict()
196-
configdb = ConfigDBConnector()
197-
configdb.connect()
199+
if self.configdb is None:
200+
configdb = ConfigDBConnector()
201+
configdb.connect()
202+
else:
203+
configdb = self.configdb
198204
sonic_cfggen.deep_update(data, sonic_cfggen.FormatConverter.db_to_output(configdb.get_config()))
199205
self.configdbJsonIn = sonic_cfggen.FormatConverter.to_serialized(data)
200206
self.sysLog(syslog.LOG_DEBUG, 'Reading Input from ConfigDB {}'.\
@@ -214,8 +220,11 @@ def writeConfigDB(self, jDiff):
214220
'''
215221
self.sysLog(doPrint=True, msg='Writing in Config DB')
216222
data = dict()
217-
configdb = ConfigDBConnector()
218-
configdb.connect(False)
223+
if self.configdb is None:
224+
configdb = ConfigDBConnector()
225+
configdb.connect(False)
226+
else:
227+
configdb = self.configdb
219228
sonic_cfggen.deep_update(data, sonic_cfggen.FormatConverter.to_deserialized(jDiff))
220229
self.sysLog(msg="Write in DB: {}".format(data))
221230
configdb.mod_config(sonic_cfggen.FormatConverter.output_to_db(data))

config/main.py

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1777,36 +1777,45 @@ def override_config_table(db, input_config_db, dry_run):
17771777
fg='magenta')
17781778
sys.exit(1)
17791779

1780-
config_db = db.cfgdb
1781-
1782-
# Read config from configDB
1783-
current_config = config_db.get_config()
1784-
# Serialize to the same format as json input
1785-
sonic_cfggen.FormatConverter.to_serialized(current_config)
1786-
1787-
updated_config = update_config(current_config, config_input)
1780+
cfgdb_clients = db.cfgdb_clients
1781+
1782+
for ns, config_db in cfgdb_clients.items():
1783+
# Read config from configDB
1784+
current_config = config_db.get_config()
1785+
# Serialize to the same format as json input
1786+
sonic_cfggen.FormatConverter.to_serialized(current_config)
1787+
1788+
if multi_asic.is_multi_asic():
1789+
# Golden Config will use "localhost" to represent host name
1790+
if ns == DEFAULT_NAMESPACE:
1791+
ns_config_input = config_input["localhost"]
1792+
else:
1793+
ns_config_input = config_input[ns]
1794+
else:
1795+
ns_config_input = config_input
1796+
updated_config = update_config(current_config, ns_config_input)
17881797

1789-
yang_enabled = device_info.is_yang_config_validation_enabled(config_db)
1790-
if yang_enabled:
1791-
# The ConfigMgmt will load YANG and running
1792-
# config during initialization.
1793-
try:
1794-
cm = ConfigMgmt()
1795-
cm.validateConfigData()
1796-
except Exception as ex:
1797-
click.secho("Failed to validate running config. Error: {}".format(ex), fg="magenta")
1798-
sys.exit(1)
1798+
yang_enabled = device_info.is_yang_config_validation_enabled(config_db)
1799+
if yang_enabled:
1800+
# The ConfigMgmt will load YANG and running
1801+
# config during initialization.
1802+
try:
1803+
cm = ConfigMgmt(configdb=config_db)
1804+
cm.validateConfigData()
1805+
except Exception as ex:
1806+
click.secho("Failed to validate running config. Error: {}".format(ex), fg="magenta")
1807+
sys.exit(1)
17991808

1800-
# Validate input config
1801-
validate_config_by_cm(cm, config_input, "config_input")
1802-
# Validate updated whole config
1803-
validate_config_by_cm(cm, updated_config, "updated_config")
1809+
# Validate input config
1810+
validate_config_by_cm(cm, ns_config_input, "config_input")
1811+
# Validate updated whole config
1812+
validate_config_by_cm(cm, updated_config, "updated_config")
18041813

1805-
if dry_run:
1806-
print(json.dumps(updated_config, sort_keys=True,
1807-
indent=4, cls=minigraph_encoder))
1808-
else:
1809-
override_config_db(config_db, config_input)
1814+
if dry_run:
1815+
print(json.dumps(updated_config, sort_keys=True,
1816+
indent=4, cls=minigraph_encoder))
1817+
else:
1818+
override_config_db(config_db, ns_config_input)
18101819

18111820

18121821
def validate_config_by_cm(cm, config_json, jname):
Lines changed: 11 additions & 0 deletions
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+
}
Lines changed: 23 additions & 0 deletions
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

Lines changed: 75 additions & 2 deletions
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)