Skip to content

Commit

Permalink
[config]Improve config save cli to save to one file for multiasic
Browse files Browse the repository at this point in the history
  • Loading branch information
wen587 committed Apr 24, 2024
1 parent 3c489ba commit a30ce28
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 3 deletions.
27 changes: 24 additions & 3 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1237,18 +1237,29 @@ def save(filename):
if filename is not None:
cfg_files = filename.split(',')

if len(cfg_files) != num_cfg_file:
# If only one filename is provided in multi-ASIC mode,
# save all ASIC configurations to that single file.
if len(cfg_files) == 1 and multi_asic.is_multi_asic():
single_file_mode = True
filename = cfg_files[0]
cfg_files = [filename] * num_cfg_file
elif len(cfg_files) != num_cfg_file:
click.echo("Input {} config file(s) separated by comma for multiple files ".format(num_cfg_file))
return
else:
single_file_mode = False

# List to hold config DB for all ASICs
all_files_config = {}

# In case of multi-asic mode we have additional config_db{NS}.json files for
# various namespaces created per ASIC. {NS} is the namespace index.
for inst in range(-1, num_cfg_file-1):
#inst = -1, refers to the linux host where there is no namespace.
if inst == -1:
namespace = None
asic_name = "localhost"
else:
namespace = "{}{}".format(NAMESPACE_PREFIX, inst)
asic_name = namespace

# Get the file from user input, else take the default file /etc/sonic/config_db{NS_id}.json
if cfg_files:
Expand All @@ -1271,6 +1282,16 @@ def save(filename):
with open(file, 'w') as config_db_file:
json.dump(config_db, config_db_file, indent=4)

# Append config DB to all_files_config
all_files_config[asic_name] = config_db

# If filename is provided and multi-ASIC mode, save all ASICs' configurations to a single file
if filename is not None and multi_asic.is_multi_asic():
if single_file_mode:
click.echo("Integrate each ASIC's config into a single JSON file {}.".format(filename))
with open(filename, 'w') as all_files_file:
json.dump(all_files_config, all_files_file, indent=4)

@config.command()
@click.option('-y', '--yes', is_flag=True)
@click.argument('filename', required=False)
Expand Down
5 changes: 5 additions & 0 deletions tests/config_save_output/all_config_db.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"localhost": {},
"asic0": {},
"asic1": {}
}
210 changes: 210 additions & 0 deletions tests/config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,34 @@
Reloading Monit configuration ...
"""

save_config_output="""\
Running command: /usr/local/bin/sonic-cfggen -d --print-data > /etc/sonic/config_db.json
"""

save_config_filename_output="""\
Running command: /usr/local/bin/sonic-cfggen -d --print-data > /tmp/config_db.json
"""

save_config_masic_output="""\
Running command: /usr/local/bin/sonic-cfggen -d --print-data > /etc/sonic/config_db.json
Running command: /usr/local/bin/sonic-cfggen -n asic0 -d --print-data > /etc/sonic/config_db0.json
Running command: /usr/local/bin/sonic-cfggen -n asic1 -d --print-data > /etc/sonic/config_db1.json
"""

save_config_filename_masic_output="""\
Running command: /usr/local/bin/sonic-cfggen -d --print-data > config_db.json
Running command: /usr/local/bin/sonic-cfggen -n asic0 -d --print-data > config_db0.json
Running command: /usr/local/bin/sonic-cfggen -n asic1 -d --print-data > config_db1.json
"""

save_config_onefile_masic_output="""\
Running command: /usr/local/bin/sonic-cfggen -d --print-data > /tmp/all_config_db.json
Running command: /usr/local/bin/sonic-cfggen -n asic0 -d --print-data > /tmp/all_config_db.json
Running command: /usr/local/bin/sonic-cfggen -n asic1 -d --print-data > /tmp/all_config_db.json
Integrate each ASIC's config into a single JSON file /tmp/all_config_db.json.
"""


def mock_run_command_side_effect(*args, **kwargs):
command = args[0]
if isinstance(command, str):
Expand Down Expand Up @@ -299,6 +327,188 @@ def test_plattform_fw_update(self, mock_check_call):
assert result.exit_code == 0
mock_check_call.assert_called_with(["fwutil", "update", 'update', 'module', 'Module1', 'component', 'BIOS', 'fw'])


class TestConfigSave(object):
@classmethod
def setup_class(cls):
os.environ['UTILITIES_UNIT_TESTING'] = "1"
print("SETUP")
import config.main
importlib.reload(config.main)


def test_config_save(self, get_cmd_module, setup_single_broadcom_asic):
def read_json_file_side_effect(filename):
return {}

with mock.patch("utilities_common.cli.run_command", mock.MagicMock(side_effect=mock_run_command_side_effect)) as mock_run_command,\
mock.patch('config.main.read_json_file', mock.MagicMock(side_effect=read_json_file_side_effect)),\
mock.patch('builtins.open', mock.MagicMock()) as mocked_open:
(config, show) = get_cmd_module

runner = CliRunner()

result = runner.invoke(config.config.commands["save"], ["-y"])

print(result.exit_code)
print(result.output)
traceback.print_tb(result.exc_info[2])

assert result.exit_code == 0
assert "\n".join([l.rstrip() for l in result.output.split('\n')]) == save_config_output

def test_config_save_filename(self, get_cmd_module, setup_single_broadcom_asic):
def read_json_file_side_effect(filename):
return {}

with mock.patch("utilities_common.cli.run_command", mock.MagicMock(side_effect=mock_run_command_side_effect)) as mock_run_command,\
mock.patch('config.main.read_json_file', mock.MagicMock(side_effect=read_json_file_side_effect)),\
mock.patch('builtins.open', mock.MagicMock()) as mocked_open:

(config, show) = get_cmd_module

runner = CliRunner()

output_file = os.path.join(os.sep, "tmp", "config_db.json")
result = runner.invoke(config.config.commands["save"], ["-y", output_file])

print(result.exit_code)
print(result.output)
traceback.print_tb(result.exc_info[2])

assert result.exit_code == 0
assert "\n".join([l.rstrip() for l in result.output.split('\n')]) == save_config_filename_output

@classmethod
def teardown_class(cls):
print("TEARDOWN")
os.environ['UTILITIES_UNIT_TESTING'] = "0"


class TestConfigSaveMasic(object):
@classmethod
def setup_class(cls):
print("SETUP")
os.environ['UTILITIES_UNIT_TESTING'] = "2"
os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "multi_asic"
import config.main
importlib.reload(config.main)
# change to multi asic config
from .mock_tables import dbconnector
from .mock_tables import mock_multi_asic
importlib.reload(mock_multi_asic)
dbconnector.load_namespace_config()

def test_config_save_masic(self, get_cmd_module, setup_multi_broadcom_masic):
def read_json_file_side_effect(filename):
return {}

with mock.patch("utilities_common.cli.run_command", mock.MagicMock(side_effect=mock_run_command_side_effect)) as mock_run_command,\
mock.patch('config.main.read_json_file', mock.MagicMock(side_effect=read_json_file_side_effect)),\
mock.patch('builtins.open', mock.MagicMock()) as mocked_open:
(config, show) = get_cmd_module

runner = CliRunner()

result = runner.invoke(config.config.commands["save"], ["-y"])

print(result.exit_code)
print(result.output)
traceback.print_tb(result.exc_info[2])

assert result.exit_code == 0
assert "\n".join([l.rstrip() for l in result.output.split('\n')]) == save_config_masic_output

def test_config_save_filename_masic(self, get_cmd_module, setup_multi_broadcom_masic):
def read_json_file_side_effect(filename):
return {}

with mock.patch("utilities_common.cli.run_command", mock.MagicMock(side_effect=mock_run_command_side_effect)) as mock_run_command,\
mock.patch('config.main.read_json_file', mock.MagicMock(side_effect=read_json_file_side_effect)),\
mock.patch('builtins.open', mock.MagicMock()) as mocked_open:
(config, show) = get_cmd_module

runner = CliRunner()

result = runner.invoke(
config.config.commands["save"],
["-y", "config_db.json,config_db0.json,config_db1.json"]
)

print(result.exit_code)
print(result.output)
traceback.print_tb(result.exc_info[2])

assert result.exit_code == 0
assert "\n".join([l.rstrip() for l in result.output.split('\n')]) == save_config_filename_masic_output

def test_config_save_filename_wrong_cnt_masic(self, get_cmd_module, setup_multi_broadcom_masic):
def read_json_file_side_effect(filename):
return {}

with mock.patch("utilities_common.cli.run_command", mock.MagicMock(side_effect=mock_run_command_side_effect)) as mock_run_command,\
mock.patch('config.main.read_json_file', mock.MagicMock(side_effect=read_json_file_side_effect)),\
mock.patch('builtins.open', mock.MagicMock()) as mocked_open:
(config, show) = get_cmd_module

runner = CliRunner()

result = runner.invoke(
config.config.commands["save"],
["-y", "config_db.json,config_db0.json"]
)

print(result.exit_code)
print(result.output)
traceback.print_tb(result.exc_info[2])

assert "Input 3 config file(s) separated by comma for multiple files" in result.output

def test_config_save_onefile_masic(self, get_cmd_module, setup_multi_broadcom_masic):
def read_json_file_side_effect(filename):
return {}

with mock.patch("utilities_common.cli.run_command", mock.MagicMock(side_effect=mock_run_command_side_effect)) as mock_run_command,\
mock.patch('config.main.read_json_file', mock.MagicMock(side_effect=read_json_file_side_effect)):
#mock.patch('builtins.open', mock.MagicMock()) as mocked_open:
(config, show) = get_cmd_module

runner = CliRunner()

output_file = os.path.join(os.sep, "tmp", "all_config_db.json")
print("Saving output in {}".format(output_file))
try:
os.remove(output_file)
except OSError:
pass
result = runner.invoke(
config.config.commands["save"],
["-y", output_file]
)

print(result.exit_code)
print(result.output)
assert result.exit_code == 0
assert "\n".join([l.rstrip() for l in result.output.split('\n')]) == save_config_onefile_masic_output

cwd = os.path.dirname(os.path.realpath(__file__))
expected_result = os.path.join(
cwd, "config_save_output", "all_config_db.json"
)
assert filecmp.cmp(output_file, expected_result, shallow=False)

@classmethod
def teardown_class(cls):
print("TEARDOWN")
os.environ['UTILITIES_UNIT_TESTING'] = "0"
os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = ""
# change back to single asic config
from .mock_tables import dbconnector
from .mock_tables import mock_single_asic
importlib.reload(mock_single_asic)
dbconnector.load_namespace_config()


class TestConfigReload(object):
dummy_cfg_file = os.path.join(os.sep, "tmp", "config.json")

Expand Down

0 comments on commit a30ce28

Please sign in to comment.