Skip to content

Commit c98648a

Browse files
dgsudharsanStormLiangMS
authored andcommitted
[show]Fix show route return code on error (#2542)
- What I did Fix show route return command to return error code on failure cases. The parameter return_cmd=True in run_command will suppress the return code and return success even in error scenarios. - How I did it When run command is called with return_cmd = True, modified its return to include return code, which can then be used to assess if there is an error by the caller - How to verify it Added UT to verify it - Previous command output (if the output of a command-line utility has changed) root@sonic:/home/admin# show ip route 123 % Unknown command: show ip route 123 root@sonic:/home/admin# echo $? 0 - New command output (if the output of a command-line utility has changed) root@sonic:/home/admin# show ip route 123 % Unknown command: show ip route 123 root@sonic:/home/admin# echo $? 1
1 parent 0137467 commit c98648a

12 files changed

+91
-40
lines changed

config/main.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -858,13 +858,13 @@ def _stop_services():
858858

859859

860860
def _get_sonic_services():
861-
out = clicommon.run_command("systemctl list-dependencies --plain sonic.target | sed '1d'", return_cmd=True)
861+
out, _ = clicommon.run_command("systemctl list-dependencies --plain sonic.target | sed '1d'", return_cmd=True)
862862
return (unit.strip() for unit in out.splitlines())
863863

864864

865865
def _get_delayed_sonic_units(get_timers=False):
866-
rc1 = clicommon.run_command("systemctl list-dependencies --plain sonic-delayed.target | sed '1d'", return_cmd=True)
867-
rc2 = clicommon.run_command("systemctl is-enabled {}".format(rc1.replace("\n", " ")), return_cmd=True)
866+
rc1, _ = clicommon.run_command("systemctl list-dependencies --plain sonic-delayed.target | sed '1d'", return_cmd=True)
867+
rc2, _ = clicommon.run_command("systemctl is-enabled {}".format(rc1.replace("\n", " ")), return_cmd=True)
868868
timer = [line.strip() for line in rc1.splitlines()]
869869
state = [line.strip() for line in rc2.splitlines()]
870870
services = []
@@ -899,16 +899,16 @@ def _restart_services():
899899

900900
def _delay_timers_elapsed():
901901
for timer in _get_delayed_sonic_units(get_timers=True):
902-
out = clicommon.run_command("systemctl show {} --property=LastTriggerUSecMonotonic --value".format(timer), return_cmd=True)
902+
out, _ = clicommon.run_command("systemctl show {} --property=LastTriggerUSecMonotonic --value".format(timer), return_cmd=True)
903903
if out.strip() == "0":
904904
return False
905905
return True
906906

907907
def _per_namespace_swss_ready(service_name):
908-
out = clicommon.run_command("systemctl show {} --property ActiveState --value".format(service_name), return_cmd=True)
908+
out, _ = clicommon.run_command("systemctl show {} --property ActiveState --value".format(service_name), return_cmd=True)
909909
if out.strip() != "active":
910910
return False
911-
out = clicommon.run_command("systemctl show {} --property ActiveEnterTimestampMonotonic --value".format(service_name), return_cmd=True)
911+
out, _ = clicommon.run_command("systemctl show {} --property ActiveEnterTimestampMonotonic --value".format(service_name), return_cmd=True)
912912
swss_up_time = float(out.strip())/1000000
913913
now = time.monotonic()
914914
if (now - swss_up_time > 120):
@@ -933,7 +933,7 @@ def _swss_ready():
933933
return True
934934

935935
def _is_system_starting():
936-
out = clicommon.run_command("sudo systemctl is-system-running", return_cmd=True)
936+
out, _ = clicommon.run_command("sudo systemctl is-system-running", return_cmd=True)
937937
return out.strip() == "starting"
938938

939939
def interface_is_in_vlan(vlan_member_table, interface_name):
@@ -2813,7 +2813,7 @@ def _qos_update_ports(ctx, ports, dry_run, json_data):
28132813
command = "{} {} {} -t {},config-db -t {},config-db -y {} --print-data".format(
28142814
SONIC_CFGGEN_PATH, cmd_ns, from_db, buffer_template_file, qos_template_file, sonic_version_file
28152815
)
2816-
jsonstr = clicommon.run_command(command, display_cmd=False, return_cmd=True)
2816+
jsonstr, _ = clicommon.run_command(command, display_cmd=False, return_cmd=True)
28172817

28182818
jsondict = json.loads(jsonstr)
28192819
port_table = jsondict.get('PORT')

config/vlan.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def restart_ndppd():
8787
ndppd_config_gen_cmd = "sonic-cfggen -d -t /usr/share/sonic/templates/ndppd.conf.j2,/etc/ndppd.conf"
8888
ndppd_restart_cmd = "supervisorctl restart ndppd"
8989

90-
output = clicommon.run_command(verify_swss_running_cmd, return_cmd=True)
90+
output, _ = clicommon.run_command(verify_swss_running_cmd, return_cmd=True)
9191

9292
if output and output.strip() != "running":
9393
click.echo(click.style('SWSS container is not running, changes will take effect the next time the SWSS container starts', fg='red'),)

scripts/sonic-bootchart

+4-2
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,13 @@ def check_bootchart_installed():
5050

5151
def get_enabled_status():
5252
""" Get systemd-bootchart status """
53-
return clicommon.run_command("systemctl is-enabled systemd-bootchart", return_cmd=True)
53+
output, _ = clicommon.run_command("systemctl is-enabled systemd-bootchart", return_cmd=True)
54+
return output
5455

5556
def get_active_status():
5657
""" Get systemd-bootchart status """
57-
return clicommon.run_command("systemctl is-active systemd-bootchart", return_cmd=True)
58+
output, _ = clicommon.run_command("systemctl is-active systemd-bootchart", return_cmd=True)
59+
return output
5860

5961
def get_output_files():
6062
bootchart_output_files = []

show/kdump.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def get_kdump_oper_mode():
4949
returns "Not Ready";
5050
"""
5151
oper_mode = "Not Ready"
52-
command_stdout = clicommon.run_command("/usr/sbin/kdump-config status", return_cmd=True)
52+
command_stdout, _ = clicommon.run_command("/usr/sbin/kdump-config status", return_cmd=True)
5353

5454
for line in command_stdout.splitlines():
5555
if ": ready to kdump" in line:
@@ -99,7 +99,7 @@ def get_kdump_core_files():
9999
dump_file_list = []
100100
cmd_message = None
101101

102-
command_stdout = clicommon.run_command(find_core_dump_files, return_cmd=True)
102+
command_stdout, _ = clicommon.run_command(find_core_dump_files, return_cmd=True)
103103

104104
dump_file_list = command_stdout.splitlines()
105105
if not dump_file_list:
@@ -123,7 +123,7 @@ def get_kdump_dmesg_files():
123123
dmesg_file_list = []
124124
cmd_message = None
125125

126-
command_stdout = clicommon.run_command(find_dmesg_files, return_cmd=True)
126+
command_stdout, _ = clicommon.run_command(find_dmesg_files, return_cmd=True)
127127

128128
dmesg_file_list = command_stdout.splitlines()
129129
if not dmesg_file_list:

tests/config_test.py

+19-19
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,13 @@ def mock_run_command_side_effect(*args, **kwargs):
142142

143143
if kwargs.get('return_cmd'):
144144
if command == "systemctl list-dependencies --plain sonic-delayed.target | sed '1d'":
145-
return 'snmp.timer'
145+
return 'snmp.timer' , 0
146146
elif command == "systemctl list-dependencies --plain sonic.target | sed '1d'":
147-
return 'swss'
147+
return 'swss', 0
148148
elif command == "systemctl is-enabled snmp.timer":
149-
return 'enabled'
149+
return 'enabled', 0
150150
else:
151-
return ''
151+
return '', 0
152152

153153
def mock_run_command_side_effect_disabled_timer(*args, **kwargs):
154154
command = args[0]
@@ -158,17 +158,17 @@ def mock_run_command_side_effect_disabled_timer(*args, **kwargs):
158158

159159
if kwargs.get('return_cmd'):
160160
if command == "systemctl list-dependencies --plain sonic-delayed.target | sed '1d'":
161-
return 'snmp.timer'
161+
return 'snmp.timer', 0
162162
elif command == "systemctl list-dependencies --plain sonic.target | sed '1d'":
163-
return 'swss'
163+
return 'swss', 0
164164
elif command == "systemctl is-enabled snmp.timer":
165-
return 'masked'
165+
return 'masked', 0
166166
elif command == "systemctl show swss.service --property ActiveState --value":
167-
return 'active'
167+
return 'active', 0
168168
elif command == "systemctl show swss.service --property ActiveEnterTimestampMonotonic --value":
169-
return '0'
169+
return '0', 0
170170
else:
171-
return ''
171+
return '', 0
172172

173173
def mock_run_command_side_effect_untriggered_timer(*args, **kwargs):
174174
command = args[0]
@@ -178,15 +178,15 @@ def mock_run_command_side_effect_untriggered_timer(*args, **kwargs):
178178

179179
if kwargs.get('return_cmd'):
180180
if command == "systemctl list-dependencies --plain sonic-delayed.target | sed '1d'":
181-
return 'snmp.timer'
181+
return 'snmp.timer', 0
182182
elif command == "systemctl list-dependencies --plain sonic.target | sed '1d'":
183-
return 'swss'
183+
return 'swss', 0
184184
elif command == "systemctl is-enabled snmp.timer":
185-
return 'enabled'
185+
return 'enabled', 0
186186
elif command == "systemctl show snmp.timer --property=LastTriggerUSecMonotonic --value":
187-
return '0'
187+
return '0', 0
188188
else:
189-
return ''
189+
return '', 0
190190

191191
def mock_run_command_side_effect_gnmi(*args, **kwargs):
192192
command = args[0]
@@ -196,13 +196,13 @@ def mock_run_command_side_effect_gnmi(*args, **kwargs):
196196

197197
if kwargs.get('return_cmd'):
198198
if command == "systemctl list-dependencies --plain sonic-delayed.target | sed '1d'":
199-
return 'gnmi.timer'
199+
return 'gnmi.timer', 0
200200
elif command == "systemctl list-dependencies --plain sonic.target | sed '1d'":
201-
return 'swss'
201+
return 'swss', 0
202202
elif command == "systemctl is-enabled gnmi.timer":
203-
return 'enabled'
203+
return 'enabled', 0
204204
else:
205-
return ''
205+
return '', 0
206206

207207

208208
# Load sonic-cfggen from source since /usr/local/bin/sonic-cfggen does not have .py extension.

tests/conftest.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,13 @@ def mock_show_bgp_summary(
145145
bgp_mocked_json = os.path.join(
146146
test_path, 'mock_tables', 'ipv6_bgp_summary_chassis.json')
147147

148+
_old_run_bgp_command = bgp_util.run_bgp_command
148149
bgp_util.run_bgp_command = mock.MagicMock(
149150
return_value=mock_show_bgp_summary("", ""))
150151

152+
yield
153+
bgp_util.run_bgp_command = _old_run_bgp_command
154+
151155

152156
@pytest.fixture
153157
def setup_t1_topo():
@@ -213,6 +217,7 @@ def mock_run_bgp_command(vtysh_cmd, bgp_namespace, vtysh_shell_cmd=constants.RVT
213217
else:
214218
return ""
215219

220+
_old_run_bgp_command = bgp_util.run_bgp_command
216221
if any ([request.param == 'ip_route',\
217222
request.param == 'ip_specific_route', request.param == 'ip_special_route',\
218223
request.param == 'ipv6_route', request.param == 'ipv6_specific_route']):
@@ -230,7 +235,6 @@ def mock_run_bgp_command(vtysh_cmd, bgp_namespace, vtysh_shell_cmd=constants.RVT
230235
bgp_util.run_bgp_command = mock.MagicMock(
231236
return_value=mock_show_bgp_network_single_asic(request))
232237
elif request.param == 'ip_route_for_int_ip':
233-
_old_run_bgp_command = bgp_util.run_bgp_command
234238
bgp_util.run_bgp_command = mock_run_bgp_command_for_static
235239
elif request.param == "show_bgp_summary_no_neigh":
236240
bgp_util.run_bgp_command = mock.MagicMock(
@@ -241,8 +245,7 @@ def mock_run_bgp_command(vtysh_cmd, bgp_namespace, vtysh_shell_cmd=constants.RVT
241245

242246
yield
243247

244-
if request.param == 'ip_route_for_int_ip':
245-
bgp_util.run_bgp_command = _old_run_bgp_command
248+
bgp_util.run_bgp_command = _old_run_bgp_command
246249

247250

248251
@pytest.fixture

tests/ip_config_test.py

+9
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import show.main as show
1313
import config.validated_config_db_connector as validated_config_db_connector
1414
from utilities_common.db import Db
15+
import utilities_common.bgp_util as bgp_util
1516

1617
test_path = os.path.dirname(os.path.abspath(__file__))
1718
ip_config_input_path = os.path.join(test_path, "ip_config_input")
@@ -33,13 +34,20 @@
3334
"""
3435

3536
class TestConfigIP(object):
37+
_old_run_bgp_command = None
3638
@classmethod
3739
def setup_class(cls):
3840
os.environ['UTILITIES_UNIT_TESTING'] = "1"
41+
cls._old_run_bgp_command = bgp_util.run_bgp_command
42+
bgp_util.run_bgp_command = mock.MagicMock(
43+
return_value=cls.mock_run_bgp_command())
3944
print("SETUP")
4045

4146
''' Tests for IPv4 '''
4247

48+
def mock_run_bgp_command():
49+
return ""
50+
4351
def test_add_del_interface_valid_ipv4(self):
4452
db = Db()
4553
runner = CliRunner()
@@ -330,4 +338,5 @@ def test_del_vrf_invalid_configdb_yang_validation(self):
330338
@classmethod
331339
def teardown_class(cls):
332340
os.environ['UTILITIES_UNIT_TESTING'] = "0"
341+
bgp_util.run_bgp_command = cls._old_run_bgp_command
333342
print("TEARDOWN")

tests/ip_show_routes_test.py

+24
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33

44
from . import show_ip_route_common
55
from click.testing import CliRunner
6+
import mock
7+
import sys
68

79
test_path = os.path.dirname(os.path.abspath(__file__))
810
modules_path = os.path.dirname(test_path)
911
scripts_path = os.path.join(modules_path, "scripts")
1012

13+
sys.path.insert(0, test_path)
14+
1115

1216
class TestShowIpRouteCommands(object):
1317
@classmethod
@@ -18,6 +22,25 @@ def setup_class(cls):
1822
os.environ["UTILITIES_UNIT_TESTING"] = "0"
1923
os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = ""
2024
import mock_tables.dbconnector
25+
26+
def test_show_ip_route_err(
27+
self,
28+
setup_ip_route_commands):
29+
show = setup_ip_route_commands
30+
31+
def mock_run_bgp_command(*args, **kwargs):
32+
command = args[0]
33+
return "% Unknown command: show ip route unknown", 1
34+
35+
with mock.patch('utilities_common.cli.run_command', mock.MagicMock(side_effect=mock_run_bgp_command)) as mock_run_command:
36+
runner = CliRunner()
37+
result = runner.invoke(
38+
show.cli.commands["ip"].commands["route"], ["unknown"])
39+
print("{}".format(result.output))
40+
print(result.exit_code)
41+
assert result.exit_code == 1
42+
assert result.output == "% Unknown command: show ip route unknown" + "\n"
43+
2144
@pytest.mark.parametrize('setup_single_bgp_instance',
2245
['ip_route'], indirect=['setup_single_bgp_instance'])
2346
def test_show_ip_route(
@@ -117,3 +140,4 @@ def test_show_ipv6_route_err(
117140
print("{}".format(result.output))
118141
assert result.exit_code == 0
119142
assert result.output == show_ip_route_common.show_ipv6_route_err_expected_output + "\n"
143+

tests/sonic_bootchart_test.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ def test_disable(self, mock_run_command):
5151
def test_config_show(self, mock_run_command):
5252
def run_command_side_effect(command, **kwargs):
5353
if "is-enabled" in command:
54-
return "enabled"
54+
return "enabled", 0
5555
elif "is-active" in command:
56-
return "active"
56+
return "active", 0
5757
else:
5858
raise Exception("unknown command")
5959

tests/vlan_test.py

+9
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import show.main as show
99
from utilities_common.db import Db
1010
from importlib import reload
11+
import utilities_common.bgp_util as bgp_util
1112

1213
show_vlan_brief_output="""\
1314
+-----------+-----------------+-----------------+----------------+-------------+
@@ -143,16 +144,23 @@
143144
+-----------+-----------------+-----------------+----------------+-------------+
144145
"""
145146
class TestVlan(object):
147+
_old_run_bgp_command = None
146148
@classmethod
147149
def setup_class(cls):
148150
os.environ['UTILITIES_UNIT_TESTING'] = "1"
149151
# ensure that we are working with single asic config
152+
cls._old_run_bgp_command = bgp_util.run_bgp_command
153+
bgp_util.run_bgp_command = mock.MagicMock(
154+
return_value=cls.mock_run_bgp_command())
150155
from .mock_tables import dbconnector
151156
from .mock_tables import mock_single_asic
152157
reload(mock_single_asic)
153158
dbconnector.load_namespace_config()
154159
print("SETUP")
155160

161+
def mock_run_bgp_command():
162+
return ""
163+
156164
def test_show_vlan(self):
157165
runner = CliRunner()
158166
result = runner.invoke(show.cli.commands["vlan"], [])
@@ -579,4 +587,5 @@ def test_config_vlan_add_member_of_portchannel(self):
579587
@classmethod
580588
def teardown_class(cls):
581589
os.environ['UTILITIES_UNIT_TESTING'] = "0"
590+
bgp_util.run_bgp_command = cls._old_run_bgp_command
582591
print("TEARDOWN")

utilities_common/bgp_util.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import ipaddress
22
import json
33
import re
4+
import sys
45

56
import click
67
import utilities_common.cli as clicommon
@@ -185,7 +186,10 @@ def run_bgp_command(vtysh_cmd, bgp_namespace=multi_asic.DEFAULT_NAMESPACE, vtysh
185186
cmd = 'sudo {} {} -c "{}"'.format(
186187
vtysh_shell_cmd, bgp_instance_id, vtysh_cmd)
187188
try:
188-
output = clicommon.run_command(cmd, return_cmd=True)
189+
output, ret = clicommon.run_command(cmd, return_cmd=True)
190+
if ret != 0:
191+
click.echo(output.rstrip('\n'))
192+
sys.exit(ret)
189193
except Exception:
190194
ctx = click.get_current_context()
191195
ctx.fail("Unable to get summary from bgp {}".format(bgp_instance_id))

utilities_common/cli.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ def run_command(command, display_cmd=False, ignore_error=False, return_cmd=False
539539

540540
if return_cmd:
541541
output = proc.communicate()[0]
542-
return output
542+
return output, proc.returncode
543543

544544
if not interactive_mode:
545545
(out, err) = proc.communicate()

0 commit comments

Comments
 (0)