diff --git a/show/main.py b/show/main.py index 0fce8037ea..209c6af3b3 100755 --- a/show/main.py +++ b/show/main.py @@ -66,6 +66,8 @@ PLATFORM_JSON = 'platform.json' HWSKU_JSON = 'hwsku.json' PORT_STR = "Ethernet" +ECMP_CALC = '/usr/bin/ecmp_calc.py' +CONTAINER_NAME = 'syncd' VLAN_SUB_INTERFACE_SEPARATOR = '.' @@ -1033,6 +1035,37 @@ def fib(ipaddress, verbose): cmd += " -ip {}".format(ipaddress) run_command(cmd, display_cmd=verbose) +# +# 'ecmp-egress-port' subcommand ("show ip ecmp-egress-port...") +# + +@ip.command() +@click.option('--packet', required=True, help="Json file describing a packet") +@click.option('--ingress-port', required=True, help="Ingress port") +@click.option('--vrf', required=False, help="VRF name") +@click.option('--debug', required=False, is_flag=True, help="Run in debug mode") +def ecmp_egress_port(packet, ingress_port, vrf, debug): + """Show egress port for given packet""" + + # Check if ECMP calculaor is implemented + proc = subprocess.run(['docker', 'exec', '-it', CONTAINER_NAME, 'test', '-f', ECMP_CALC]) + if proc.returncode != 0: + click.echo("ECMP calculator is not available in this image") + return + + # Copy packet JSON to syncd + cmd = "docker cp {} {}:/".format(packet, CONTAINER_NAME) + run_command(cmd) + + # Call ECMP calculaor + packet = os.path.basename(packet) + cmd = "docker exec {} {} --packet {} --interface {}".format(CONTAINER_NAME, ECMP_CALC, packet, ingress_port) + if vrf is not None: + cmd += " --vrf {}".format(vrf) + if debug is True: + cmd += " --debug" + run_command(cmd, display_cmd=debug) + # # 'ipv6' group ("show ipv6 ...") diff --git a/tests/ecmp_calc_test.py b/tests/ecmp_calc_test.py new file mode 100644 index 0000000000..f0b447e154 --- /dev/null +++ b/tests/ecmp_calc_test.py @@ -0,0 +1,39 @@ +import os +import subprocess +import show.main as show +from unittest import mock +from click.testing import CliRunner + +FILE_FOUND_RC = 0 +FILE_NOT_FOUND_RC= 1 +CONTAINER_NAME = 'syncd' +ECMP_CALC = '/usr/bin/ecmp_calc.py' +SHOW_CMD_ARGS = '--ingress-port Ethernet0 --packet /packet.json --vrf Vrf_red --debug' + +class TestShowIpEcmpEgressPort(object): + @classmethod + def setup_class(cls): + os.environ["UTILITIES_UNIT_TESTING"] = "1" + + @mock.patch('subprocess.run', mock.MagicMock(return_value = subprocess.CompletedProcess('', FILE_FOUND_RC, None, None))) + def test_valid_flow(self): + with mock.patch("show.main.run_command", mock.MagicMock()) as mock_run_command: + runner = CliRunner() + result = runner.invoke(show.cli.commands["ip"].commands["ecmp-egress-port"], SHOW_CMD_ARGS.split()) + assert result.exit_code == 0 + assert mock_run_command.call_count == 2 + + ecmp_calc_cmd = "docker exec {} {} --packet packet.json --interface Ethernet0 --vrf Vrf_red --debug".format(CONTAINER_NAME, ECMP_CALC) + mock_run_command.assert_called_with(ecmp_calc_cmd, display_cmd=True) + + @mock.patch('subprocess.run', mock.MagicMock(return_value = subprocess.CompletedProcess('', FILE_NOT_FOUND_RC, None, None))) + def test_binary_not_found(self): + with mock.patch("show.main.run_command", mock.MagicMock()) as mock_run_command: + runner = CliRunner() + result = runner.invoke(show.cli.commands["ip"].commands["ecmp-egress-port"], SHOW_CMD_ARGS.split()) + assert result.exit_code == 0 + assert mock_run_command.call_count == 0 + + @classmethod + def teardown_class(cls): + os.environ["UTILITIES_UNIT_TESTING"] = "0"