Skip to content

Commit 12add72

Browse files
wen587arfeigin
authored andcommitted
[config]Improve config save cli to save to one file for multiasic (sonic-net#3288)
HLD design : sonic-net/SONiC#1684 #### What I did Add support for config save to one file for multi-aisc. #### How I did it Extend support for one file save for multiasic using the below format: ``` { "localhost": {/*host config*/}, "asic0": {/*asic0 config*/}, ... "asicN": {/*asicN config*/} } ``` #### How to verify it Unit test and manual test on multiasic platform. Example running multi: ``` admin@str2-8800-sup-2:~$ sudo config save -y tmp.json Integrate each ASIC's config into a single JSON file tmp.json. admin@str2-8800-sup-2:~$ cat tmp.json |more { "localhost": { "ACL_TABLE": { "NTP_ACL": { "policy_desc": "NTP_ACL", "services": [ "NTP" ... "asic0": { "AUTO_TECHSUPPORT": { "GLOBAL": { "available_mem_threshold": "10.0", ```
1 parent c401b57 commit 12add72

File tree

3 files changed

+240
-3
lines changed

3 files changed

+240
-3
lines changed

config/main.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,6 +1157,22 @@ def validate_gre_type(ctx, _, value):
11571157
except ValueError:
11581158
raise click.UsageError("{} is not a valid GRE type".format(value))
11591159

1160+
1161+
def multi_asic_save_config(db, filename):
1162+
"""A function to save all asic's config to single file
1163+
"""
1164+
all_current_config = {}
1165+
cfgdb_clients = db.cfgdb_clients
1166+
1167+
for ns, config_db in cfgdb_clients.items():
1168+
current_config = config_db.get_config()
1169+
sonic_cfggen.FormatConverter.to_serialized(current_config)
1170+
asic_name = "localhost" if ns == DEFAULT_NAMESPACE else ns
1171+
all_current_config[asic_name] = sort_dict(current_config)
1172+
click.echo("Integrate each ASIC's config into a single JSON file {}.".format(filename))
1173+
with open(filename, 'w') as file:
1174+
json.dump(all_current_config, file, indent=4)
1175+
11601176
# Function to apply patch for a single ASIC.
11611177
def apply_patch_for_scope(scope_changes, results, config_format, verbose, dry_run, ignore_non_yang_tables, ignore_path):
11621178
scope, changes = scope_changes
@@ -1276,7 +1292,8 @@ def config(ctx):
12761292
@click.option('-y', '--yes', is_flag=True, callback=_abort_if_false,
12771293
expose_value=False, prompt='Existing files will be overwritten, continue?')
12781294
@click.argument('filename', required=False)
1279-
def save(filename):
1295+
@clicommon.pass_db
1296+
def save(db, filename):
12801297
"""Export current config DB to a file on disk.\n
12811298
<filename> : Names of configuration file(s) to save, separated by comma with no spaces in between
12821299
"""
@@ -1291,7 +1308,13 @@ def save(filename):
12911308
if filename is not None:
12921309
cfg_files = filename.split(',')
12931310

1294-
if len(cfg_files) != num_cfg_file:
1311+
# If only one filename is provided in multi-ASIC mode,
1312+
# save all ASIC configurations to that single file.
1313+
if len(cfg_files) == 1 and multi_asic.is_multi_asic():
1314+
filename = cfg_files[0]
1315+
multi_asic_save_config(db, filename)
1316+
return
1317+
elif len(cfg_files) != num_cfg_file:
12951318
click.echo("Input {} config file(s) separated by comma for multiple files ".format(num_cfg_file))
12961319
return
12971320

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"localhost": {},
3+
"asic0": {},
4+
"asic1": {}
5+
}

tests/config_test.py

Lines changed: 210 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,30 @@
168168
Reloading Monit configuration ...
169169
"""
170170

171+
save_config_output = """\
172+
Running command: /usr/local/bin/sonic-cfggen -d --print-data > /etc/sonic/config_db.json
173+
"""
174+
175+
save_config_filename_output = """\
176+
Running command: /usr/local/bin/sonic-cfggen -d --print-data > /tmp/config_db.json
177+
"""
178+
179+
save_config_masic_output = """\
180+
Running command: /usr/local/bin/sonic-cfggen -d --print-data > /etc/sonic/config_db.json
181+
Running command: /usr/local/bin/sonic-cfggen -n asic0 -d --print-data > /etc/sonic/config_db0.json
182+
Running command: /usr/local/bin/sonic-cfggen -n asic1 -d --print-data > /etc/sonic/config_db1.json
183+
"""
184+
185+
save_config_filename_masic_output = """\
186+
Running command: /usr/local/bin/sonic-cfggen -d --print-data > config_db.json
187+
Running command: /usr/local/bin/sonic-cfggen -n asic0 -d --print-data > config_db0.json
188+
Running command: /usr/local/bin/sonic-cfggen -n asic1 -d --print-data > config_db1.json
189+
"""
190+
191+
save_config_onefile_masic_output = """\
192+
Integrate each ASIC's config into a single JSON file /tmp/all_config_db.json.
193+
"""
194+
171195
config_temp = {
172196
"scope": {
173197
"ACL_TABLE": {
@@ -333,6 +357,191 @@ def test_plattform_fw_update(self, mock_check_call):
333357
assert result.exit_code == 0
334358
mock_check_call.assert_called_with(["fwutil", "update", 'update', 'module', 'Module1', 'component', 'BIOS', 'fw'])
335359

360+
361+
class TestConfigSave(object):
362+
@classmethod
363+
def setup_class(cls):
364+
os.environ['UTILITIES_UNIT_TESTING'] = "1"
365+
print("SETUP")
366+
import config.main
367+
importlib.reload(config.main)
368+
369+
def test_config_save(self, get_cmd_module, setup_single_broadcom_asic):
370+
def read_json_file_side_effect(filename):
371+
return {}
372+
373+
with mock.patch("utilities_common.cli.run_command",
374+
mock.MagicMock(side_effect=mock_run_command_side_effect)),\
375+
mock.patch('config.main.read_json_file',
376+
mock.MagicMock(side_effect=read_json_file_side_effect)),\
377+
mock.patch('config.main.open',
378+
mock.MagicMock()):
379+
(config, show) = get_cmd_module
380+
381+
runner = CliRunner()
382+
383+
result = runner.invoke(config.config.commands["save"], ["-y"])
384+
385+
print(result.exit_code)
386+
print(result.output)
387+
traceback.print_tb(result.exc_info[2])
388+
389+
assert result.exit_code == 0
390+
assert "\n".join([li.rstrip() for li in result.output.split('\n')]) == save_config_output
391+
392+
def test_config_save_filename(self, get_cmd_module, setup_single_broadcom_asic):
393+
def read_json_file_side_effect(filename):
394+
return {}
395+
396+
with mock.patch("utilities_common.cli.run_command",
397+
mock.MagicMock(side_effect=mock_run_command_side_effect)),\
398+
mock.patch('config.main.read_json_file',
399+
mock.MagicMock(side_effect=read_json_file_side_effect)),\
400+
mock.patch('config.main.open',
401+
mock.MagicMock()):
402+
403+
(config, show) = get_cmd_module
404+
405+
runner = CliRunner()
406+
407+
output_file = os.path.join(os.sep, "tmp", "config_db.json")
408+
result = runner.invoke(config.config.commands["save"], ["-y", output_file])
409+
410+
print(result.exit_code)
411+
print(result.output)
412+
traceback.print_tb(result.exc_info[2])
413+
414+
assert result.exit_code == 0
415+
assert "\n".join([li.rstrip() for li in result.output.split('\n')]) == save_config_filename_output
416+
417+
@classmethod
418+
def teardown_class(cls):
419+
print("TEARDOWN")
420+
os.environ['UTILITIES_UNIT_TESTING'] = "0"
421+
422+
423+
class TestConfigSaveMasic(object):
424+
@classmethod
425+
def setup_class(cls):
426+
print("SETUP")
427+
os.environ['UTILITIES_UNIT_TESTING'] = "2"
428+
os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "multi_asic"
429+
import config.main
430+
importlib.reload(config.main)
431+
# change to multi asic config
432+
from .mock_tables import dbconnector
433+
from .mock_tables import mock_multi_asic
434+
importlib.reload(mock_multi_asic)
435+
dbconnector.load_namespace_config()
436+
437+
def test_config_save_masic(self):
438+
def read_json_file_side_effect(filename):
439+
return {}
440+
441+
with mock.patch("utilities_common.cli.run_command",
442+
mock.MagicMock(side_effect=mock_run_command_side_effect)),\
443+
mock.patch('config.main.read_json_file',
444+
mock.MagicMock(side_effect=read_json_file_side_effect)),\
445+
mock.patch('config.main.open',
446+
mock.MagicMock()):
447+
448+
runner = CliRunner()
449+
450+
result = runner.invoke(config.config.commands["save"], ["-y"])
451+
452+
print(result.exit_code)
453+
print(result.output)
454+
traceback.print_tb(result.exc_info[2])
455+
456+
assert result.exit_code == 0
457+
assert "\n".join([li.rstrip() for li in result.output.split('\n')]) == save_config_masic_output
458+
459+
def test_config_save_filename_masic(self):
460+
def read_json_file_side_effect(filename):
461+
return {}
462+
463+
with mock.patch("utilities_common.cli.run_command",
464+
mock.MagicMock(side_effect=mock_run_command_side_effect)),\
465+
mock.patch('config.main.read_json_file',
466+
mock.MagicMock(side_effect=read_json_file_side_effect)),\
467+
mock.patch('config.main.open',
468+
mock.MagicMock()):
469+
470+
runner = CliRunner()
471+
472+
result = runner.invoke(
473+
config.config.commands["save"],
474+
["-y", "config_db.json,config_db0.json,config_db1.json"]
475+
)
476+
477+
print(result.exit_code)
478+
print(result.output)
479+
traceback.print_tb(result.exc_info[2])
480+
481+
assert result.exit_code == 0
482+
assert "\n".join([li.rstrip() for li in result.output.split('\n')]) == save_config_filename_masic_output
483+
484+
def test_config_save_filename_wrong_cnt_masic(self):
485+
def read_json_file_side_effect(filename):
486+
return {}
487+
488+
with mock.patch('config.main.read_json_file', mock.MagicMock(side_effect=read_json_file_side_effect)):
489+
490+
runner = CliRunner()
491+
492+
result = runner.invoke(
493+
config.config.commands["save"],
494+
["-y", "config_db.json,config_db0.json"]
495+
)
496+
497+
print(result.exit_code)
498+
print(result.output)
499+
traceback.print_tb(result.exc_info[2])
500+
501+
assert "Input 3 config file(s) separated by comma for multiple files" in result.output
502+
503+
def test_config_save_onefile_masic(self):
504+
def get_config_side_effect():
505+
return {}
506+
507+
with mock.patch('swsscommon.swsscommon.ConfigDBConnector.get_config',
508+
mock.MagicMock(side_effect=get_config_side_effect)):
509+
runner = CliRunner()
510+
511+
output_file = os.path.join(os.sep, "tmp", "all_config_db.json")
512+
print("Saving output in {}".format(output_file))
513+
try:
514+
os.remove(output_file)
515+
except OSError:
516+
pass
517+
result = runner.invoke(
518+
config.config.commands["save"],
519+
["-y", output_file]
520+
)
521+
522+
print(result.exit_code)
523+
print(result.output)
524+
assert result.exit_code == 0
525+
assert "\n".join([li.rstrip() for li in result.output.split('\n')]) == save_config_onefile_masic_output
526+
527+
cwd = os.path.dirname(os.path.realpath(__file__))
528+
expected_result = os.path.join(
529+
cwd, "config_save_output", "all_config_db.json"
530+
)
531+
assert filecmp.cmp(output_file, expected_result, shallow=False)
532+
533+
@classmethod
534+
def teardown_class(cls):
535+
print("TEARDOWN")
536+
os.environ['UTILITIES_UNIT_TESTING'] = "0"
537+
os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = ""
538+
# change back to single asic config
539+
from .mock_tables import dbconnector
540+
from .mock_tables import mock_single_asic
541+
importlib.reload(mock_single_asic)
542+
dbconnector.load_namespace_config()
543+
544+
336545
class TestConfigReload(object):
337546
dummy_cfg_file = os.path.join(os.sep, "tmp", "config.json")
338547

@@ -2889,4 +3098,4 @@ def teardown_class(cls):
28893098
from .mock_tables import dbconnector
28903099
from .mock_tables import mock_single_asic
28913100
importlib.reload(mock_single_asic)
2892-
dbconnector.load_database_config()
3101+
dbconnector.load_database_config()

0 commit comments

Comments
 (0)