diff --git a/generic_config_updater/gu_common.py b/generic_config_updater/gu_common.py index 0cfaa73943..87fb4adfd7 100644 --- a/generic_config_updater/gu_common.py +++ b/generic_config_updater/gu_common.py @@ -48,7 +48,9 @@ def __init__(self, yang_dir = YANG_DIR): def get_config_db_as_json(self): text = self._get_config_db_as_text() - return json.loads(text) + config_db_json = json.loads(text) + config_db_json.pop("bgpraw", None) + return config_db_json def _get_config_db_as_text(self): # TODO: Getting configs from CLI is very slow, need to get it from sonic-cffgen directly diff --git a/show/main.py b/show/main.py index fc453ab899..74c5c9d899 100755 --- a/show/main.py +++ b/show/main.py @@ -10,7 +10,7 @@ import utilities_common.multi_asic as multi_asic_util from importlib import reload from natsort import natsorted -from sonic_py_common import device_info +from sonic_py_common import device_info, multi_asic from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector from tabulate import tabulate from utilities_common import util_base @@ -18,6 +18,7 @@ from datetime import datetime import utilities_common.constants as constants from utilities_common.general import load_db_config +from json.decoder import JSONDecodeError # mock the redis for unit test purposes # try: @@ -127,6 +128,10 @@ def run_command(command, display_cmd=False, return_cmd=False): if rc != 0: sys.exit(rc) +def get_cmd_output(cmd): + proc = subprocess.Popen(cmd, text=True, stdout=subprocess.PIPE) + return proc.communicate()[0], proc.returncode + # Lazy global class instance for SONiC interface name to alias conversion iface_alias_converter = lazy_object_proxy.Proxy(lambda: clicommon.InterfaceAliasConverter()) @@ -1229,8 +1234,25 @@ def runningconfiguration(): @click.option('--verbose', is_flag=True, help="Enable verbose output") def all(verbose): """Show full running configuration""" - cmd = "sonic-cfggen -d --print-data" - run_command(cmd, display_cmd=verbose) + cmd = ['sonic-cfggen', '-d', '--print-data'] + stdout, rc = get_cmd_output(cmd) + if rc: + click.echo("Failed to get cmd output '{}':rc {}".format(cmd, rc)) + raise click.Abort() + + try: + output = json.loads(stdout) + except JSONDecodeError as e: + click.echo("Failed to load output '{}':{}".format(cmd, e)) + raise click.Abort() + + if not multi_asic.is_multi_asic(): + bgpraw_cmd = [constants.RVTYSH_COMMAND, '-c', 'show running-config'] + bgpraw, rc = get_cmd_output(bgpraw_cmd) + if rc: + bgpraw = "" + output['bgpraw'] = bgpraw + click.echo(json.dumps(output, indent=4)) # 'acl' subcommand ("show runningconfiguration acl") diff --git a/tests/generic_config_updater/gu_common_test.py b/tests/generic_config_updater/gu_common_test.py index 6ba4923664..a767e641a5 100644 --- a/tests/generic_config_updater/gu_common_test.py +++ b/tests/generic_config_updater/gu_common_test.py @@ -3,12 +3,24 @@ import jsonpatch import sonic_yang import unittest -from unittest.mock import MagicMock, Mock +from unittest.mock import MagicMock, Mock, patch from .gutest_helpers import create_side_effect_dict, Files import generic_config_updater.gu_common as gu_common class TestDryRunConfigWrapper(unittest.TestCase): + @patch('generic_config_updater.gu_common.subprocess.Popen') + def test_get_config_db_as_json(self, mock_popen): + config_wrapper = gu_common.DryRunConfigWrapper() + mock_proc = MagicMock() + mock_proc.communicate = MagicMock( + return_value=('{"PORT": {}, "bgpraw": ""}', None)) + mock_proc.returncode = 0 + mock_popen.return_value = mock_proc + actual = config_wrapper.get_config_db_as_json() + expected = {"PORT": {}} + self.assertDictEqual(actual, expected) + def test_get_config_db_as_json__returns_imitated_config_db(self): # Arrange config_wrapper = gu_common.DryRunConfigWrapper(Files.CONFIG_DB_AS_JSON) diff --git a/tests/show_test.py b/tests/show_test.py new file mode 100644 index 0000000000..87c1b5a17e --- /dev/null +++ b/tests/show_test.py @@ -0,0 +1,51 @@ +import os +import sys +import show.main as show +from click.testing import CliRunner +from unittest import mock +from unittest.mock import call, MagicMock + +test_path = os.path.dirname(os.path.abspath(__file__)) +modules_path = os.path.dirname(test_path) +sys.path.insert(0, test_path) +sys.path.insert(0, modules_path) + + +class TestShowRunAllCommands(object): + @classmethod + def setup_class(cls): + print("SETUP") + os.environ["UTILITIES_UNIT_TESTING"] = "1" + + def test_show_runningconfiguration_all_json_loads_failure(self): + def get_cmd_output_side_effect(*args, **kwargs): + return "", 0 + with mock.patch('show.main.get_cmd_output', + mock.MagicMock(side_effect=get_cmd_output_side_effect)) as mock_get_cmd_output: + result = CliRunner().invoke(show.cli.commands['runningconfiguration'].commands['all'], []) + assert result.exit_code != 0 + + def test_show_runningconfiguration_all_get_cmd_ouput_failure(self): + def get_cmd_output_side_effect(*args, **kwargs): + return "{}", 2 + with mock.patch('show.main.get_cmd_output', + mock.MagicMock(side_effect=get_cmd_output_side_effect)) as mock_get_cmd_output: + result = CliRunner().invoke(show.cli.commands['runningconfiguration'].commands['all'], []) + assert result.exit_code != 0 + + def test_show_runningconfiguration_all(self): + def get_cmd_output_side_effect(*args, **kwargs): + return "{}", 0 + with mock.patch('show.main.get_cmd_output', + mock.MagicMock(side_effect=get_cmd_output_side_effect)) as mock_get_cmd_output: + result = CliRunner().invoke(show.cli.commands['runningconfiguration'].commands['all'], []) + assert mock_get_cmd_output.call_count == 2 + assert mock_get_cmd_output.call_args_list == [ + call(['sonic-cfggen', '-d', '--print-data']), + call(['rvtysh', '-c', 'show running-config'])] + + @classmethod + def teardown_class(cls): + print("TEARDOWN") + os.environ["PATH"] = os.pathsep.join(os.environ["PATH"].split(os.pathsep)[:-1]) + os.environ["UTILITIES_UNIT_TESTING"] = "0"