diff --git a/show/main.py b/show/main.py index 117958f83a..ff158ea595 100755 --- a/show/main.py +++ b/show/main.py @@ -20,6 +20,7 @@ import utilities_common.constants as constants from utilities_common.general import load_db_config from json.decoder import JSONDecodeError +from sonic_py_common.general import getstatusoutput_noshell_pipe # mock the redis for unit test purposes # try: @@ -105,6 +106,8 @@ def readJsonFile(fileName): return result def run_command(command, display_cmd=False, return_cmd=False): + command_list = command + command = ' '.join(command) if display_cmd: click.echo(click.style("Command: ", fg='cyan') + click.style(command, fg='green')) @@ -114,7 +117,7 @@ def run_command(command, display_cmd=False, return_cmd=False): clicommon.run_command_in_alias_mode(command) raise sys.exit(0) - proc = subprocess.Popen(command, shell=True, text=True, stdout=subprocess.PIPE) + proc = subprocess.Popen(command_list, text=True, stdout=subprocess.PIPE) while True: if return_cmd: @@ -130,6 +133,32 @@ def run_command(command, display_cmd=False, return_cmd=False): if rc != 0: sys.exit(rc) +def run_command_pipe(*args, display_cmd=False, return_cmd=False): + command_lists = [' '.join(arg) for arg in args] + command = ' | '.join(command_lists) + if display_cmd: + click.echo(click.style("Command: ", fg='cyan') + click.style(command, fg='green')) + + # No conversion needed for intfutil commands as it already displays + # both SONiC interface name and alias name for all interfaces. + if clicommon.get_interface_naming_mode() == "alias" and not command.startswith("intfutil"): + clicommon.run_command_in_alias_mode(command) + raise sys.exit(0) + + exitcodes, output = getstatusoutput_noshell_pipe(*args) + + while True: + if return_cmd: + return output + output = proc.stdout.readline() + if output: + click.echo(output.rstrip('\n')) + + if any(exitcodes): + for rc in exitcodes: + 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 @@ -377,10 +406,10 @@ def event_counters(): @click.option('--verbose', is_flag=True, help="Enable verbose output") def arp(ipaddress, iface, verbose): """Show IP ARP table""" - cmd = "nbrshow -4" + cmd = ['nbrshow', '-4'] if ipaddress is not None: - cmd += " -ip {}".format(ipaddress) + cmd += ['-ip', str(ipaddress)] if iface is not None: if clicommon.get_interface_naming_mode() == "alias": @@ -388,7 +417,7 @@ def arp(ipaddress, iface, verbose): (iface.startswith("eth"))): iface = iface_alias_converter.alias_to_name(iface) - cmd += " -if {}".format(iface) + cmd += ['-if', str(iface)] run_command(cmd, display_cmd=verbose) @@ -402,22 +431,22 @@ def arp(ipaddress, iface, verbose): @click.option('--verbose', is_flag=True, help="Enable verbose output") def ndp(ip6address, iface, verbose): """Show IPv6 Neighbour table""" - cmd = "nbrshow -6" + cmd = ['nbrshow', '-6'] if ip6address is not None: - cmd += " -ip {}".format(ip6address) + cmd += ['-ip', str(ip6address)] if iface is not None: - cmd += " -if {}".format(iface) + cmd += ['-if', str(iface)] run_command(cmd, display_cmd=verbose) def is_mgmt_vrf_enabled(ctx): """Check if management VRF is enabled""" if ctx.invoked_subcommand is None: - cmd = 'sonic-cfggen -d --var-json "MGMT_VRF_CONFIG"' + cmd = ['sonic-cfggen', '-d', '--var-json', "MGMT_VRF_CONFIG"] - p = subprocess.Popen(cmd, shell=True, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen(cmd, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) try : mvrf_dict = json.loads(p.stdout.read()) except ValueError: @@ -487,13 +516,13 @@ def mgmt_vrf(ctx,routes): if routes is None: click.echo("\nManagementVRF : Enabled") click.echo("\nManagement VRF interfaces in Linux:") - cmd = "ip -d link show mgmt" + cmd = ['ip', '-d', 'link', 'show', 'mgmt'] run_command(cmd) - cmd = "ip link show vrf mgmt" + cmd = ['ip', 'link', 'show', 'vrf', 'mgmt'] run_command(cmd) else: click.echo("\nRoutes in Management VRF Routing Table:") - cmd = "ip route show table 5000" + cmd = ['ip', 'route', 'show', 'table', '5000'] run_command(cmd) # @@ -577,7 +606,7 @@ def subinterfaces(): @click.option('--verbose', is_flag=True, help="Enable verbose output") def status(subinterfacename, verbose): """Show sub port interface status information""" - cmd = "intfutil -c status" + cmd = ['intfutil', '-c', 'status'] if subinterfacename is not None: sub_intf_sep_idx = subinterfacename.find(VLAN_SUB_INTERFACE_SEPARATOR) @@ -588,9 +617,9 @@ def status(subinterfacename, verbose): if clicommon.get_interface_naming_mode() == "alias": subinterfacename = iface_alias_converter.alias_to_name(subinterfacename) - cmd += " -i {}".format(subinterfacename) + cmd += ['-i', str(subinterfacename)] else: - cmd += " -i subport" + cmd += ['-i', 'subport'] run_command(cmd, display_cmd=verbose) # @@ -609,9 +638,9 @@ def pfc(): def counters(namespace, display, verbose): """Show pfc counters""" - cmd = "pfcstat -s {}".format(display) + cmd = ['pfcstat', '-s', str(display)] if namespace is not None: - cmd += " -n {}".format(namespace) + cmd += ['-n', str(namespace)] run_command(cmd, display_cmd=verbose) @@ -619,12 +648,12 @@ def counters(namespace, display, verbose): @click.argument('interface', type=click.STRING, required=False) def priority(interface): """Show pfc priority""" - cmd = 'pfc show priority' + cmd = ['pfc', 'show', 'priority'] if interface is not None and clicommon.get_interface_naming_mode() == "alias": interface = iface_alias_converter.alias_to_name(interface) if interface is not None: - cmd += ' {0}'.format(interface) + cmd += [str(interface)] run_command(cmd) @@ -632,12 +661,12 @@ def priority(interface): @click.argument('interface', type=click.STRING, required=False) def asymmetric(interface): """Show asymmetric pfc""" - cmd = 'pfc show asymmetric' + cmd = ['pfc', 'show', 'asymmetric'] if interface is not None and clicommon.get_interface_naming_mode() == "alias": interface = iface_alias_converter.alias_to_name(interface) if interface is not None: - cmd += ' {0}'.format(interface) + cmd += [str(interface)] run_command(cmd) @@ -653,9 +682,9 @@ def pfcwd(): def config(namespace, display, verbose): """Show pfc watchdog config""" - cmd = "pfcwd show config -d {}".format(display) + cmd = ['pfcwd', 'show', 'config', '-d', str(display)] if namespace is not None: - cmd += " -n {}".format(namespace) + cmd += ['-n', str(namespace)] run_command(cmd, display_cmd=verbose) @@ -665,9 +694,9 @@ def config(namespace, display, verbose): def stats(namespace, display, verbose): """Show pfc watchdog stats""" - cmd = "pfcwd show stats -d {}".format(display) + cmd = ['pfcwd', 'show', 'stats', '-d', str(display)] if namespace is not None: - cmd += " -n {}".format(namespace) + cmd += ['-n', str(namespace)] run_command(cmd, display_cmd=verbose) @@ -688,7 +717,7 @@ def telemetry(): @telemetry.command('interval') def show_tm_interval(): """Show telemetry interval""" - command = 'watermarkcfg --show-interval' + command = ['watermarkcfg', '--show-interval'] run_command(command) @@ -711,23 +740,23 @@ def queue(): def counters(interfacename, namespace, display, verbose, json, voq): """Show queue counters""" - cmd = "queuestat" + cmd = ["queuestat"] if interfacename is not None: if clicommon.get_interface_naming_mode() == "alias": interfacename = iface_alias_converter.alias_to_name(interfacename) if interfacename is not None: - cmd += " -p {}".format(interfacename) + cmd += ['-p', str(interfacename)] if namespace is not None: - cmd += " -n {}".format(namespace) + cmd += ['-n', str(namespace)] if json: - cmd += " -j" + cmd += ["-j"] if voq: - cmd += " -V" + cmd += ["-V"] run_command(cmd, display_cmd=verbose) @@ -744,21 +773,21 @@ def watermark(): @watermark.command('unicast') def wm_q_uni(): """Show user WM for unicast queues""" - command = 'watermarkstat -t q_shared_uni' + command = ['watermarkstat', '-t', 'q_shared_uni'] run_command(command) # 'multicast' subcommand ("show queue watermarks multicast") @watermark.command('multicast') def wm_q_multi(): """Show user WM for multicast queues""" - command = 'watermarkstat -t q_shared_multi' + command = ['watermarkstat', '-t', 'q_shared_multi'] run_command(command) # 'all' subcommand ("show queue watermarks all") @watermark.command('all') def wm_q_all(): """Show user WM for all queues""" - command = 'watermarkstat -t q_shared_all' + command = ['watermarkstat', '-t', 'q_shared_all'] run_command(command) # @@ -774,21 +803,21 @@ def persistent_watermark(): @persistent_watermark.command('unicast') def pwm_q_uni(): """Show persistent WM for unicast queues""" - command = 'watermarkstat -p -t q_shared_uni' + command = ['watermarkstat', '-p', '-t', 'q_shared_uni'] run_command(command) # 'multicast' subcommand ("show queue persistent-watermarks multicast") @persistent_watermark.command('multicast') def pwm_q_multi(): """Show persistent WM for multicast queues""" - command = 'watermarkstat -p -t q_shared_multi' + command = ['watermarkstat', '-p', '-t', 'q_shared_multi'] run_command(command) # 'all' subcommand ("show queue persistent-watermarks all") @persistent_watermark.command('all') def pwm_q_all(): """Show persistent WM for all queues""" - command = 'watermarkstat -p -t q_shared_all' + command = ['watermarkstat', '-p', '-t', 'q_shared_all'] run_command(command) # @@ -807,13 +836,13 @@ def watermark(): @watermark.command('headroom') def wm_pg_headroom(): """Show user headroom WM for pg""" - command = 'watermarkstat -t pg_headroom' + command = ['watermarkstat', '-t', 'pg_headroom'] run_command(command) @watermark.command('shared') def wm_pg_shared(): """Show user shared WM for pg""" - command = 'watermarkstat -t pg_shared' + command = ['watermarkstat', '-t', 'pg_shared'] run_command(command) @priority_group.group() @@ -824,7 +853,7 @@ def drop(): @drop.command('counters') def pg_drop_counters(): """Show dropped packets for priority-group""" - command = 'pg-drop -c show' + command = ['pg-drop', '-c', 'show'] run_command(command) @priority_group.group(name='persistent-watermark') @@ -835,13 +864,13 @@ def persistent_watermark(): @persistent_watermark.command('headroom') def pwm_pg_headroom(): """Show persistent headroom WM for pg""" - command = 'watermarkstat -p -t pg_headroom' + command = ['watermarkstat', '-p', '-t', 'pg_headroom'] run_command(command) @persistent_watermark.command('shared') def pwm_pg_shared(): """Show persistent shared WM for pg""" - command = 'watermarkstat -p -t pg_shared' + command = ['watermarkstat', '-p', '-t', 'pg_shared'] run_command(command) @@ -856,13 +885,13 @@ def buffer_pool(): @buffer_pool.command('watermark') def wm_buffer_pool(): """Show user WM for buffer pools""" - command = 'watermarkstat -t buffer_pool' + command = ['watermarkstat', '-t' ,'buffer_pool'] run_command(command) @buffer_pool.command('persistent-watermark') def pwm_buffer_pool(): """Show persistent WM for buffer pools""" - command = 'watermarkstat -p -t buffer_pool' + command = ['watermarkstat', '-p', '-t', 'buffer_pool'] run_command(command) @@ -877,13 +906,13 @@ def headroom_pool(): @headroom_pool.command('watermark') def wm_headroom_pool(): """Show user WM for headroom pool""" - command = 'watermarkstat -t headroom_pool' + command = ['watermarkstat', '-t', 'headroom_pool'] run_command(command) @headroom_pool.command('persistent-watermark') def pwm_headroom_pool(): """Show persistent WM for headroom pool""" - command = 'watermarkstat -p -t headroom_pool' + command = ['watermarkstat', '-p', '-t', 'headroom_pool'] run_command(command) @@ -905,22 +934,22 @@ def mac(ctx, vlan, port, address, type, count, verbose): if ctx.invoked_subcommand is not None: return - cmd = "fdbshow" + cmd = ["fdbshow"] if vlan is not None: - cmd += " -v {}".format(vlan) + cmd += ['-v', str(vlan)] if port is not None: - cmd += " -p {}".format(port) + cmd += ['-p', str(port)] if address is not None: - cmd += " -a {}".format(address) + cmd += ['-a', str(address)] if type is not None: - cmd += " -t {}".format(type) + cmd += ['-t', str(type)] if count: - cmd += " -c" + cmd += ["-c"] run_command(cmd, display_cmd=verbose) @@ -951,10 +980,9 @@ def aging_time(ctx): @click.option('--verbose', is_flag=True, help="Enable verbose output") def route_map(route_map_name, verbose): """show route-map""" - cmd = 'sudo {} -c "show route-map'.format(constants.RVTYSH_COMMAND) + cmd = ['sudo', constants.RVTYSH_COMMAND, '-c', 'show route-map'] if route_map_name is not None: - cmd += ' {}'.format(route_map_name) - cmd += '"' + cmd += [str(route_map_name)] run_command(cmd, display_cmd=verbose) # @@ -1042,10 +1070,9 @@ def route(args, namespace, display, verbose): @click.option('--verbose', is_flag=True, help="Enable verbose output") def prefix_list(prefix_list_name, verbose): """show ip prefix-list""" - cmd = 'sudo {} -c "show ip prefix-list'.format(constants.RVTYSH_COMMAND) + cmd = ['sudo', constants.RVTYSH_COMMAND, '-c', 'show ip prefix-list'] if prefix_list_name is not None: - cmd += ' {}'.format(prefix_list_name) - cmd += '"' + cmd += [str(prefix_list_name)] run_command(cmd, display_cmd=verbose) @@ -1054,7 +1081,7 @@ def prefix_list(prefix_list_name, verbose): @click.option('--verbose', is_flag=True, help="Enable verbose output") def protocol(verbose): """Show IPv4 protocol information""" - cmd = 'sudo {} -c "show ip protocol"'.format(constants.RVTYSH_COMMAND) + cmd = ['sudo', constants.rvtysh_command, '-c', "show ip protocol"] run_command(cmd, display_cmd=verbose) # @@ -1065,9 +1092,9 @@ def protocol(verbose): @click.option('--verbose', is_flag=True, help="Enable verbose output") def fib(ipaddress, verbose): """Show IP FIB table""" - cmd = "fibshow -4" + cmd = ['fibshow', '-4'] if ipaddress is not None: - cmd += " -ip {}".format(ipaddress) + cmd += ['-ip', str(ipaddress)] run_command(cmd, display_cmd=verbose) @@ -1090,10 +1117,9 @@ def ipv6(): @click.option('--verbose', is_flag=True, help="Enable verbose output") def prefix_list(prefix_list_name, verbose): """show ip prefix-list""" - cmd = 'sudo {} -c "show ipv6 prefix-list'.format(constants.RVTYSH_COMMAND) + cmd = ['sudo', constants.RVTYSH_COMMAND, '-c', 'show ipv6 prefix-list'] if prefix_list_name is not None: - cmd += ' {}'.format(prefix_list_name) - cmd += '"' + cmd += [str(prefix_list_name)] run_command(cmd, display_cmd=verbose) @@ -1138,7 +1164,7 @@ def route(args, namespace, display, verbose): @click.option('--verbose', is_flag=True, help="Enable verbose output") def protocol(verbose): """Show IPv6 protocol information""" - cmd = 'sudo {} -c "show ipv6 protocol"'.format(constants.RVTYSH_COMMAND) + cmd = ['sudo', constants.RVTYSH_COMMAND, '-c', "show ipv6 protocol"] run_command(cmd, display_cmd=verbose) # @@ -1207,9 +1233,9 @@ def link_local_mode(verbose): @click.option('--verbose', is_flag=True, help="Enable verbose output") def fib(ipaddress, verbose): """Show IP FIB table""" - cmd = "fibshow -6" + cmd = ['fibshow', '-6'] if ipaddress is not None: - cmd += " -ip {}".format(ipaddress) + cmd += ['-ip', str(ipaddress)] run_command(cmd, display_cmd=verbose) # @@ -1227,13 +1253,13 @@ def lldp(): @click.option('--verbose', is_flag=True, help="Enable verbose output") def neighbors(interfacename, verbose): """Show LLDP neighbors""" - cmd = "sudo lldpshow -d" + cmd = ['sudo', 'lldpshow', '-d'] if interfacename is not None: if clicommon.get_interface_naming_mode() == "alias": interfacename = iface_alias_converter.alias_to_name(interfacename) - cmd += " -p {}".format(interfacename) + cmd += ['-p', str(interfacename)] run_command(cmd, display_cmd=verbose) @@ -1242,7 +1268,7 @@ def neighbors(interfacename, verbose): @click.option('--verbose', is_flag=True, help="Enable verbose output") def table(verbose): """Show LLDP neighbors in tabular format""" - cmd = "sudo lldpshow" + cmd = ['sudo', 'lldpshow'] run_command(cmd, display_cmd=verbose) @@ -1257,26 +1283,34 @@ def table(verbose): @click.option('--verbose', is_flag=True, help="Enable verbose output") def logging(process, lines, follow, verbose): """Show system log""" + cmd, cmd0, cmd1, cmd2 = None, None, None, None if os.path.exists("/var/log.tmpfs"): log_path = "/var/log.tmpfs" else: log_path = "/var/log" if follow: - cmd = "sudo tail -F {}/syslog".format(log_path) + cmd = ['sudo', 'tail', '-F', '{}/syslog'.format(log_path)] run_command(cmd, display_cmd=verbose) else: if os.path.isfile("{}/syslog.1".format(log_path)): - cmd = "sudo cat {}/syslog.1 {}/syslog".format(log_path, log_path) + cmd0 = ['sudo', 'cat', '{}/syslog.1'.format(log_path), '{}/syslog'.format(log_path)] else: - cmd = "sudo cat {}/syslog".format(log_path) + cmd0 = ['sudo', 'cat', '{}/syslog'.format(log_path)] if process is not None: - cmd += " | grep '{}'".format(process) + cmd1 = ['grep', str(process)] if lines is not None: - cmd += " | tail -{}".format(lines) + cmd2 = ['tail', "-" + str(lines)] - run_command(cmd, display_cmd=verbose) + if cmd1 is None and cmd2 is None: + run_command(cmd0, display_cmd=verbose) + elif cmd1 is not None and cmd2 is None: + run_command_pipe(cmd0, cmd1, display_cmd=verbose) + elif cmd1 is None and cmd2 is not None: + run_command_pipe(cmd0, cmd2, display_cmd=verbose) + elif cmd1 is not None and cmd2 is not None: + run_command_pipe(cmd0, cmd1, cmd2, display_cmd=verbose) # @@ -1291,8 +1325,8 @@ def version(verbose): platform_info = device_info.get_platform_info() chassis_info = platform.get_chassis_info() - sys_uptime_cmd = "uptime" - sys_uptime = subprocess.Popen(sys_uptime_cmd, shell=True, text=True, stdout=subprocess.PIPE) + sys_uptime_cmd = ["uptime"] + sys_uptime = subprocess.Popen(sys_uptime_cmd, text=True, stdout=subprocess.PIPE) sys_date = datetime.now() @@ -1312,8 +1346,8 @@ def version(verbose): click.echo("Uptime: {}".format(sys_uptime.stdout.read().strip())) click.echo("Date: {}".format(sys_date.strftime("%a %d %b %Y %X"))) click.echo("\nDocker images:") - cmd = 'sudo docker images --format "table {{.Repository}}\\t{{.Tag}}\\t{{.ID}}\\t{{.Size}}"' - p = subprocess.Popen(cmd, shell=True, text=True, stdout=subprocess.PIPE) + cmd = ['sudo', 'docker', 'images', '--format', "table {{.Repository}}\\t{{.Tag}}\\t{{.ID}}\\t{{.Size}}"] + p = subprocess.Popen(cmd, text=True, stdout=subprocess.PIPE) click.echo(p.stdout.read()) # @@ -1324,7 +1358,7 @@ def version(verbose): @click.option('--verbose', is_flag=True, help="Enable verbose output") def environment(verbose): """Show environmentals (voltages, fans, temps)""" - cmd = "sudo sensors" + cmd = ['sudo', 'sensors'] run_command(cmd, display_cmd=verbose) @@ -1336,7 +1370,7 @@ def environment(verbose): @click.option('--verbose', is_flag=True, help="Enable verbose output") def users(verbose): """Show users""" - cmd = "who" + cmd = ["who"] run_command(cmd, display_cmd=verbose) @@ -1355,29 +1389,29 @@ def users(verbose): @click.option('--redirect-stderr', '-r', is_flag=True, help="Redirect an intermediate errors to STDERR") def techsupport(since, global_timeout, cmd_timeout, verbose, allow_process_stop, silent, debug_dump, redirect_stderr): """Gather information for troubleshooting""" - cmd = "sudo" + cmd = ["sudo"] if global_timeout: - cmd += " timeout --kill-after={}s -s SIGTERM --foreground {}m".format(COMMAND_TIMEOUT, global_timeout) + cmd += ['timeout', '--kill-after={}s'.format(COMMAND_TIMEOUT), '-s', 'SIGTERM', '--foreground', '{}m'.format(global_timeout)] if silent: - cmd += " generate_dump" + cmd += ["generate_dump"] click.echo("Techsupport is running with silent option. This command might take a long time.") else: - cmd += " generate_dump -v" + cmd += ['generate_dump', '-v'] if allow_process_stop: - cmd += " -a" + cmd += ["-a"] if since: - cmd += " -s '{}'".format(since) + cmd += ['-s', str(since)] if debug_dump: - cmd += " -d" + cmd += ["-d"] - cmd += " -t {}".format(cmd_timeout) + cmd += ['-t', str(cmd_timeout)] if redirect_stderr: - cmd += " -r" + cmd += ["-r"] run_command(cmd, display_cmd=verbose) @@ -1422,7 +1456,7 @@ def all(verbose): @click.option('--verbose', is_flag=True, help="Enable verbose output") def acl(verbose): """Show acl running configuration""" - cmd = "sonic-cfggen -d --var-json ACL_RULE" + cmd = ['sonic-cfggen', '-d', '--var-json', 'ACL_RULE'] run_command(cmd, display_cmd=verbose) @@ -1432,10 +1466,10 @@ def acl(verbose): @click.option('--verbose', is_flag=True, help="Enable verbose output") def ports(portname, verbose): """Show ports running configuration""" - cmd = "sonic-cfggen -d --var-json PORT" + cmd = ['sonic-cfggen', '-d', '--var-json', 'PORT'] if portname is not None: - cmd += " {0} {1}".format("--key", portname) + cmd += ["--key", str(portname)] run_command(cmd, display_cmd=verbose) @@ -1485,10 +1519,10 @@ def bgp(namespace, verbose): @click.option('--verbose', is_flag=True, help="Enable verbose output") def interfaces(interfacename, verbose): """Show interfaces running configuration""" - cmd = "sonic-cfggen -d --var-json INTERFACE" + cmd = ['sonic-cfggen', '-d', '--var-json', 'INTERFACE'] if interfacename is not None: - cmd += " {0} {1}".format("--key", interfacename) + cmd += ["--key", str(interfacename)] run_command(cmd, display_cmd=verbose) @@ -1714,16 +1748,20 @@ def startupconfiguration(): @click.option('--verbose', is_flag=True, help="Enable verbose output") def bgp(verbose): """Show BGP startup configuration""" - cmd = "sudo docker ps | grep bgp | awk '{print$2}' | cut -d'-' -f3 | cut -d':' -f1" - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True, text=True) - result = proc.stdout.read().rstrip() + cmd0 = ['sudo', 'docker', 'ps'] + cmd1 = ['grep', 'bgp'] + cmd2 = ['awk', '{print$2}'] + cmd3 = ['cut', '-d', '-', '-f3'] + cmd4 = ['cut', '-d', ':', "-f1"] + _, stdout = getstatusoutput_noshell_pipe(cmd0, cmd1, cmd2, cmd3, cmd4) + result = stdout.rstrip() click.echo("Routing-Stack is: {}".format(result)) if result == "quagga": - run_command('sudo docker exec bgp cat /etc/quagga/bgpd.conf', display_cmd=verbose) + run_command(['sudo', 'docker', 'exec', 'bgp', 'cat /etc/quagga/bgpd.conf'], display_cmd=verbose) elif result == "frr": - run_command('sudo docker exec bgp cat /etc/frr/bgpd.conf', display_cmd=verbose) + run_command(['sudo', 'docker', 'exec', 'bgp', 'cat /etc/frr/bgpd.conf'], display_cmd=verbose) elif result == "gobgp": - run_command('sudo docker exec bgp cat /etc/gpbgp/bgpd.conf', display_cmd=verbose) + run_command(['sudo', 'docker', 'exec', 'bgp', 'cat /etc/gpbgp/bgpd.conf'], display_cmd=verbose) else: click.echo("Unidentified routing-stack") @@ -1744,11 +1782,11 @@ def ntp(ctx, verbose): os_info = os.uname() release = os_info[2].split('-') if parse_version(release[0]) > parse_version("4.9.0"): - ntpstat_cmd = "sudo ip vrf exec mgmt ntpstat" - ntpcmd = "sudo ip vrf exec mgmt ntpq -p -n" + ntpstat_cmd = ['sudo', 'ip', 'vrf', 'exec', 'mgmt', 'ntpstat'] + ntpcmd = ['sudo', 'ip', 'vrf', 'exec', 'mgmt', 'ntpq', '-p', '-n'] else: - ntpstat_cmd = "sudo cgexec -g l3mdev:mgmt ntpstat" - ntpcmd = "sudo cgexec -g l3mdev:mgmt ntpq -p -n" + ntpstat_cmd = ['sudo', 'cgexec', '-g', 'l3mdev:mgmt', 'ntpstat'] + ntpcmd = ['sudo', 'cgexec', '-g', 'l3mdev:mgmt', 'ntpq', '-p', '-n'] run_command(ntpstat_cmd, display_cmd=verbose) run_command(ntpcmd, display_cmd=verbose) @@ -1761,37 +1799,38 @@ def ntp(ctx, verbose): @click.option('--verbose', is_flag=True, help="Enable verbose output") def uptime(verbose): """Show system uptime""" - cmd = "uptime -p" + cmd = ['uptime', '-p'] run_command(cmd, display_cmd=verbose) @cli.command() @click.option('--verbose', is_flag=True, help="Enable verbose output") def clock(verbose): """Show date and time""" - cmd ="date" + cmd = ["date"] run_command(cmd, display_cmd=verbose) @cli.command('system-memory') @click.option('--verbose', is_flag=True, help="Enable verbose output") def system_memory(verbose): """Show memory information""" - cmd = "free -m" + cmd = ['free', '-m'] run_command(cmd, display_cmd=verbose) @cli.command('services') def services(): """Show all daemon services""" - cmd = "sudo docker ps --format '{{.Names}}'" - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True, text=True) + cmd = ["sudo", "docker", "ps", "--format", '{{.Names}}'] + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, text=True) while True: line = proc.stdout.readline() if line != '': print(line.rstrip()+'\t'+"docker") print("---------------------------") - cmd = "sudo docker exec {} ps aux | sed '$d'".format(line.rstrip()) - proc1 = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True, text=True) - print(proc1.stdout.read()) + cmd0 = ["sudo", "docker", "exec", line.rstrip(), "ps", "aux"] + cmd1 = ["sed", '$d'] + _, stdout = getstatusoutput_noshell_pipe(cmd0, cmd1) + print(stdout) else: break @@ -1917,10 +1956,10 @@ def radius(db): @click.option('--verbose', is_flag=True, help="Enable verbose output") def mirror_session(session_name, verbose): """Show existing everflow sessions""" - cmd = "acl-loader show session" + cmd = ['acl-loader', 'show', 'session'] if session_name is not None: - cmd += " {}".format(session_name) + cmd += [str(session_name)] run_command(cmd, display_cmd=verbose) @@ -1933,10 +1972,10 @@ def mirror_session(session_name, verbose): @click.option('--verbose', is_flag=True, help="Enable verbose output") def policer(policer_name, verbose): """Show existing policers""" - cmd = "acl-loader show policer" + cmd = ['acl-loader', 'show', 'policer'] if policer_name is not None: - cmd += " {}".format(policer_name) + cmd += [str(policer_name)] run_command(cmd, display_cmd=verbose) @@ -1948,7 +1987,7 @@ def policer(policer_name, verbose): @click.option('--verbose', is_flag=True, help="Enable verbose output") def ecn(verbose): """Show ECN configuration""" - cmd = "ecnconfig -l" + cmd = ['ecnconfig', '-l'] run_command(cmd, display_cmd=verbose) @@ -1958,8 +1997,8 @@ def ecn(verbose): @cli.command('boot') def boot(): """Show boot configuration""" - cmd = "sudo sonic-installer list" - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True, text=True) + cmd = ["sudo", "sonic-installer", "list"] + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, text=True) click.echo(proc.stdout.read()) @@ -1969,7 +2008,7 @@ def boot(): @cli.command('mmu') def mmu(): """Show mmu configuration""" - cmd = "mmuconfig -l" + cmd = ['mmuconfig', '-l'] run_command(cmd) # @@ -1986,7 +2025,7 @@ def buffer(): @buffer.command() def configuration(): """show buffer configuration""" - cmd = "mmuconfig -l" + cmd = ['mmuconfig', '-l'] run_command(cmd) # @@ -1995,7 +2034,7 @@ def configuration(): @buffer.command() def information(): """show buffer information""" - cmd = "buffershow -l" + cmd = ['buffershow', '-l'] run_command(cmd) @@ -2007,7 +2046,7 @@ def information(): @click.option('--verbose', is_flag=True, help="Enable verbose output") def line(brief, verbose): """Show all console lines and their info include available ttyUSB devices unless specified brief mode""" - cmd = "consutil show" + (" -b" if brief else "") + cmd = ['consutil', 'show'] + ["-b"] if brief else [] run_command(cmd, display_cmd=verbose) return @@ -2021,11 +2060,11 @@ def line(brief, verbose): def ztp(status, verbose): """Show Zero Touch Provisioning status""" if os.path.isfile('/usr/bin/ztp') is False: - exit("ZTP feature unavailable in this image version") + sys.exit("ZTP feature unavailable in this image version") - cmd = "ztp status" + cmd = ['ztp', 'status'] if verbose: - cmd = cmd + " --verbose" + cmd += ["--verbose"] run_command(cmd, display_cmd=verbose) diff --git a/show/platform.py b/show/platform.py index 1916e10d84..e5bef2660b 100644 --- a/show/platform.py +++ b/show/platform.py @@ -148,9 +148,9 @@ def temperature(): @click.argument('args', nargs=-1, type=click.UNPROCESSED) def firmware(args): """Show firmware information""" - cmd = "sudo fwutil show {}".format(" ".join(args)) + cmd = ["sudo", "fwutil", "show"] + list(args) try: - subprocess.check_call(cmd, shell=True) + subprocess.check_call(cmd) except subprocess.CalledProcessError as e: sys.exit(e.returncode) diff --git a/show/plugins/barefoot.py b/show/plugins/barefoot.py index bc80c47ba3..b601d96803 100644 --- a/show/plugins/barefoot.py +++ b/show/plugins/barefoot.py @@ -4,6 +4,7 @@ import json import subprocess from sonic_py_common import device_info +from sonic_py_common.general import getstatusoutput_noshell_pipe, check_output_pipe @click.group() def barefoot(): @@ -25,8 +26,9 @@ def profile(): # Print current profile click.echo('Current profile: ', nl=False) - subprocess.run('docker exec -it syncd readlink /opt/bfn/install | sed ' - r's/install_\\\(.\*\\\)_profile/\\1/', check=True, shell=True) + cmd0 = ['docker', 'exec', '-it', 'syncd', 'readlink', '/opt/bfn/install'] + cmd1 = ['sed', r's/install_\\\(.\*\\\)_profile/\\1/'] + check_output_pipe(cmd0, cmd1) # Exclude current and unsupported profiles opts = '' @@ -37,9 +39,10 @@ def profile(): # Print profile list click.echo('Available profile(s):') - subprocess.run('docker exec -it syncd find /opt/bfn -mindepth 1 ' - r'-maxdepth 1 -type d -name install_\*_profile ' + opts + '| sed ' - r's%/opt/bfn/install_\\\(.\*\\\)_profile%\\1%', shell=True) + cmd0 = ['docker', 'exec', '-it', 'syncd', 'find', '/opt/bfn', '-mindepth', '1',\ + r'-maxdepth', '1', '-type', 'd', '-name', 'install_\*_profile', r'{}' % opts] + cmd1 = ["sed", r's%/opt/bfn/install_\\\(.\*\\\)_profile%\\1%'] + getstatusoutput_noshell_pipe(cmd0, cmd1) def register(cli): version_info = device_info.get_sonic_version_info() diff --git a/show/plugins/mlnx.py b/show/plugins/mlnx.py index cefe0cbd01..96fab40002 100644 --- a/show/plugins/mlnx.py +++ b/show/plugins/mlnx.py @@ -26,7 +26,8 @@ import sys import subprocess import click - import xml.etree.ElementTree as ET + from shlex import join + from lxml import etree as ET from sonic_py_common import device_info except ImportError as e: raise ImportError("%s - required module not found" % str(e)) @@ -45,10 +46,11 @@ def run_command(command, display_cmd=False, ignore_error=False, print_to_console=True): """Run bash command and print output to stdout """ - if display_cmd == True: - click.echo(click.style("Running command: ", fg='cyan') + click.style(command, fg='green')) + if display_cmd: + command_str = join(command) + click.echo(click.style("Running command: ", fg='cyan') + click.style(command_str, fg='green')) - proc = subprocess.Popen(command, shell=True, text=True, stdout=subprocess.PIPE) + proc = subprocess.Popen(command, text=True, stdout=subprocess.PIPE) (out, err) = proc.communicate() if len(out) > 0 and print_to_console: @@ -70,9 +72,9 @@ def mlnx(): # get current status of sniffer from conf file def sniffer_status_get(env_variable_name): enabled = False - command = "docker exec {} bash -c 'touch {}'".format(CONTAINER_NAME, SNIFFER_CONF_FILE) + command = ["docker", "exec", CONTAINER_NAME, "bash", "-c", 'touch {}'.format(SNIFFER_CONF_FILE)] run_command(command) - command = 'docker cp {} {}'.format(SNIFFER_CONF_FILE_IN_CONTAINER, TMP_SNIFFER_CONF_FILE) + command = ['docker', 'cp', SNIFFER_CONF_FILE_IN_CONTAINER, TMP_SNIFFER_CONF_FILE] run_command(command) conf_file = open(TMP_SNIFFER_CONF_FILE, 'r') for env_variable_string in conf_file: @@ -80,7 +82,7 @@ def sniffer_status_get(env_variable_name): enabled = True break conf_file.close() - command = 'rm -rf {}'.format(TMP_SNIFFER_CONF_FILE) + command = ['rm', '-rf', TMP_SNIFFER_CONF_FILE] run_command(command) return enabled @@ -97,10 +99,8 @@ def is_issu_status_enabled(): # Get the SAI XML path from sai.profile sai_profile_path = '/{}/sai.profile'.format(HWSKU_PATH) - DOCKER_CAT_COMMAND = 'docker exec {container_name} cat {path}' - - command = DOCKER_CAT_COMMAND.format(container_name=CONTAINER_NAME, path=sai_profile_path) - sai_profile_content, _ = run_command(command, print_to_console=False) + DOCKER_CAT_COMMAND = ['docker', 'exec', CONTAINER_NAME, 'cat', sai_profile_path] + sai_profile_content, _ = run_command(DOCKER_CAT_COMMAND, print_to_console=False) sai_profile_kvs = {} @@ -117,8 +117,8 @@ def is_issu_status_enabled(): sys.exit(1) # Get ISSU from SAI XML - command = DOCKER_CAT_COMMAND.format(container_name=CONTAINER_NAME, path=sai_xml_path) - sai_xml_content, _ = run_command(command, print_to_console=False) + DOCKER_CAT_COMMAND = ['docker', 'exec', CONTAINER_NAME, 'cat', sai_xml_path] + sai_xml_content, _ = run_command(DOCKER_CAT_COMMAND, print_to_console=False) try: root = ET.fromstring(sai_xml_content) diff --git a/tests/fdbshow_test.py b/tests/fdbshow_test.py index 8814a6b323..e8ce42fed1 100755 --- a/tests/fdbshow_test.py +++ b/tests/fdbshow_test.py @@ -450,7 +450,7 @@ def test_show_mac_no_address(self): def test_show_mac_no_type(self): self.set_mock_variant("6") - result = self.runner.invoke(show.cli.commands["mac"], ["-t Static"]) + result = self.runner.invoke(show.cli.commands["mac"], ["-t", "Static"]) print(result.exit_code) print(result.output) assert result.exit_code == 0 diff --git a/tests/intfstat_test.py b/tests/intfstat_test.py index 4522e08311..f76e54c7b5 100644 --- a/tests/intfstat_test.py +++ b/tests/intfstat_test.py @@ -168,7 +168,7 @@ def test_clear_single_intfs(self): result = runner.invoke(show.cli.commands["interfaces"].commands["counters"].commands["rif"], ["Ethernet20"]) print(result.output) # remove the counters snapshot - show.run_command("intfstat -D") + show.run_command(["intfstat", "-D"]) assert 'Last cached time was' in result.output.split('\n')[0] assert show_interfaces_counters_rif_clear_single_intf in result.output @@ -180,7 +180,7 @@ def test_clear_single_interface_check_all(self): result = runner.invoke(show.cli.commands["interfaces"].commands["counters"].commands["rif"], []) print(result.stdout) # remove the counters snapshot - show.run_command("intfstat -D") + show.run_command(["intfstat", "-D"]) assert 'Last cached time was' in result.output.split('\n')[0] assert show_single_interface_check_all_clear in result.output @@ -192,7 +192,7 @@ def test_clear(self): result = runner.invoke(show.cli.commands["interfaces"].commands["counters"].commands["rif"], []) print(result.stdout) # remove the counters snapshot - show.run_command("intfstat -D") + show.run_command(["intfstat", "-D"]) assert 'Last cached time was' in result.output.split('\n')[0] assert show_interfaces_counters_rif_clear in result.output diff --git a/tests/queue_counter_test.py b/tests/queue_counter_test.py index b7b3637126..5c435d4ad7 100644 --- a/tests/queue_counter_test.py +++ b/tests/queue_counter_test.py @@ -1199,7 +1199,7 @@ def test_queue_counters_port_json(self): runner = CliRunner() result = runner.invoke( show.cli.commands["queue"].commands["counters"], - ["Ethernet8 --json"] + ["Ethernet8", "--json"] ) assert result.exit_code == 0 print(result.output) @@ -1224,7 +1224,7 @@ def test_queue_port_voq_counters(self): runner = CliRunner() result = runner.invoke( show.cli.commands["queue"].commands["counters"], - ["Ethernet0 --voq"] + ["Ethernet0", "--voq"] ) print(result.output) assert result.exit_code == 0 diff --git a/tests/show_test.py b/tests/show_test.py index 114dbc3c6c..598e3b36aa 100644 --- a/tests/show_test.py +++ b/tests/show_test.py @@ -6,7 +6,7 @@ from unittest import mock from unittest.mock import call, MagicMock, patch -EXPECTED_BASE_COMMAND = 'sudo ' +EXPECTED_BASE_COMMAND = ['sudo'] test_path = os.path.dirname(os.path.abspath(__file__)) modules_path = os.path.dirname(test_path) @@ -54,65 +54,98 @@ def teardown_class(cls): os.environ["UTILITIES_UNIT_TESTING"] = "0" @patch('show.main.run_command') +@patch('show.main.run_command_pipe') @pytest.mark.parametrize( "cli_arguments,expected", [ - ([], 'cat /var/log/syslog'), - (['xcvrd'], "cat /var/log/syslog | grep 'xcvrd'"), - (['-l', '10'], 'cat /var/log/syslog | tail -10'), - (['-f'], 'tail -F /var/log/syslog'), + ([], ['cat', '/var/log/syslog']), + (['-f'], ['tail', '-F', '/var/log/syslog']), ] ) -def test_show_logging_default(run_command, cli_arguments, expected): +@pytest.mark.parametrize( + "cli_arguments1,expected0,expected1", + [ + (['xcvrd'], ['cat', '/var/log/syslog'], ['grep', 'xcvrd']), + (['-l', '10'], ['cat', '/var/log/syslog'], ['tail', '-10']), + ] +) +def test_show_logging_default(run_cmd_pipe, run_cmd, cli_arguments, expected, cli_arguments1, expected0, expected1): runner = CliRunner() result = runner.invoke(show.cli.commands["logging"], cli_arguments) - run_command.assert_called_with(EXPECTED_BASE_COMMAND + expected, display_cmd=False) + run_cmd.assert_called_with(EXPECTED_BASE_COMMAND + expected, display_cmd=False) + result = runner.invoke(show.cli.commands["logging"], cli_arguments1) + run_cmd_pipe.assert_called_with(EXPECTED_BASE_COMMAND + expected0, expected1, display_cmd=False) @patch('show.main.run_command') +@patch('show.main.run_command_pipe') @patch('os.path.isfile', MagicMock(return_value=True)) @pytest.mark.parametrize( "cli_arguments,expected", [ - ([], 'cat /var/log/syslog.1 /var/log/syslog'), - (['xcvrd'], "cat /var/log/syslog.1 /var/log/syslog | grep 'xcvrd'"), - (['-l', '10'], 'cat /var/log/syslog.1 /var/log/syslog | tail -10'), - (['-f'], 'tail -F /var/log/syslog'), + ([], ['cat', '/var/log/syslog.1', '/var/log/syslog']), + (['-f'], ['tail', '-F', '/var/log/syslog']), ] ) -def test_show_logging_syslog_1(run_command, cli_arguments, expected): +@pytest.mark.parametrize( + "cli_arguments1,expected0,expected1", + [ + (['xcvrd'], ['cat', '/var/log/syslog.1', '/var/log/syslog'], ['grep', 'xcvrd']), + (['-l', '10'], ['cat', '/var/log/syslog.1', '/var/log/syslog'], ['tail', '-10']), + ] +) + +def test_show_logging_syslog_1(run_cmd_pipe, run_cmd, cli_arguments, expected, cli_arguments1, expected0, expected1): runner = CliRunner() result = runner.invoke(show.cli.commands["logging"], cli_arguments) - run_command.assert_called_with(EXPECTED_BASE_COMMAND + expected, display_cmd=False) + run_cmd.assert_called_with(EXPECTED_BASE_COMMAND + expected, display_cmd=False) + result = runner.invoke(show.cli.commands["logging"], cli_arguments1) + run_cmd_pipe.assert_called_with(EXPECTED_BASE_COMMAND + expected0, expected1, display_cmd=False) @patch('show.main.run_command') +@patch('show.main.run_command_pipe') @patch('os.path.exists', MagicMock(return_value=True)) @pytest.mark.parametrize( "cli_arguments,expected", [ - ([], 'cat /var/log.tmpfs/syslog'), - (['xcvrd'], "cat /var/log.tmpfs/syslog | grep 'xcvrd'"), - (['-l', '10'], 'cat /var/log.tmpfs/syslog | tail -10'), - (['-f'], 'tail -F /var/log.tmpfs/syslog'), + ([], ['cat', '/var/log.tmpfs/syslog']), + (['-f'], ['tail', '-F', '/var/log.tmpfs/syslog']), ] ) -def test_show_logging_tmpfs(run_command, cli_arguments, expected): +@pytest.mark.parametrize( + "cli_arguments1,expected0,expected1", + [ + (['xcvrd'], ['cat', '/var/log.tmpfs/syslog'], ['grep', 'xcvrd']), + (['-l', '10'], ['cat', '/var/log.tmpfs/syslog'], ['tail', '-10']), + ] +) +def test_show_logging_tmpfs(run_cmd_pipe, run_cmd, cli_arguments, expected, cli_arguments1, expected0, expected1): runner = CliRunner() result = runner.invoke(show.cli.commands["logging"], cli_arguments) - run_command.assert_called_with(EXPECTED_BASE_COMMAND + expected, display_cmd=False) + run_cmd.assert_called_with(EXPECTED_BASE_COMMAND + expected, display_cmd=False) + result = runner.invoke(show.cli.commands["logging"], cli_arguments1) + run_cmd_pipe.assert_called_with(EXPECTED_BASE_COMMAND + expected0, expected1, display_cmd=False) @patch('show.main.run_command') +@patch('show.main.run_command_pipe') @patch('os.path.isfile', MagicMock(return_value=True)) @patch('os.path.exists', MagicMock(return_value=True)) @pytest.mark.parametrize( "cli_arguments,expected", [ - ([], 'cat /var/log.tmpfs/syslog.1 /var/log.tmpfs/syslog'), - (['xcvrd'], "cat /var/log.tmpfs/syslog.1 /var/log.tmpfs/syslog | grep 'xcvrd'"), - (['-l', '10'], 'cat /var/log.tmpfs/syslog.1 /var/log.tmpfs/syslog | tail -10'), - (['-f'], 'tail -F /var/log.tmpfs/syslog'), + ([], ['cat', '/var/log.tmpfs/syslog.1', '/var/log.tmpfs/syslog']), + (['-f'], ['tail', '-F', '/var/log.tmpfs/syslog']), + ] +) +@pytest.mark.parametrize( + "cli_arguments1,expected0,expected1", + [ + (['xcvrd'], ['cat', '/var/log.tmpfs/syslog.1', '/var/log.tmpfs/syslog'], ['grep', 'xcvrd']), + (['-l', '10'], ['cat', '/var/log.tmpfs/syslog.1', '/var/log.tmpfs/syslog'], ['tail', '-10']), ] ) -def test_show_logging_tmpfs_syslog_1(run_command, cli_arguments, expected): +def test_show_logging_tmpfs_syslog_1(run_cmd_pipe, run_cmd, cli_arguments, expected, cli_arguments1, expected0, expected1): runner = CliRunner() result = runner.invoke(show.cli.commands["logging"], cli_arguments) - run_command.assert_called_with(EXPECTED_BASE_COMMAND + expected, display_cmd=False) + run_cmd.assert_called_with(EXPECTED_BASE_COMMAND + expected, display_cmd=False) + result = runner.invoke(show.cli.commands["logging"], cli_arguments1) + run_cmd_pipe.assert_called_with(EXPECTED_BASE_COMMAND + expected0, expected1, display_cmd=False) diff --git a/tests/techsupport_test.py b/tests/techsupport_test.py index 41664e3589..c5f65895a2 100644 --- a/tests/techsupport_test.py +++ b/tests/techsupport_test.py @@ -3,18 +3,18 @@ from unittest.mock import patch, Mock from click.testing import CliRunner -EXPECTED_BASE_COMMAND = 'sudo ' +EXPECTED_BASE_COMMAND = ['sudo'] @patch("show.main.run_command") @pytest.mark.parametrize( "cli_arguments,expected", [ - ([], 'generate_dump -v -t 5'), - (['--since', '2 days ago'], "generate_dump -v -s '2 days ago' -t 5"), - (['-g', '50'], 'timeout --kill-after=300s -s SIGTERM --foreground 50m generate_dump -v -t 5'), - (['--allow-process-stop'], 'generate_dump -v -a -t 5'), - (['--silent'], 'generate_dump -t 5'), - (['--debug-dump', '--redirect-stderr'], 'generate_dump -v -d -t 5 -r'), + ([], ['generate_dump', '-v', '-t', '5']), + (['--since', '2 days ago'], ['generate_dump', '-v', '-s', '2 days ago', '-t', '5']), + (['-g', '50'], ['timeout', '--kill-after=300s', '-s', 'SIGTERM', '--foreground', '50m', 'generate_dump', '-v', '-t', '5']), + (['--allow-process-stop'], ['generate_dump', '-v', '-a', '-t', '5']), + (['--silent'], ['generate_dump', '-t', '5']), + (['--debug-dump', '--redirect-stderr'], ['generate_dump', '-v', '-d', '-t', '5', '-r']), ] ) def test_techsupport(run_command, cli_arguments, expected): diff --git a/tests/tunnelstat_test.py b/tests/tunnelstat_test.py index f1fe716ef3..19df51f2a6 100644 --- a/tests/tunnelstat_test.py +++ b/tests/tunnelstat_test.py @@ -83,7 +83,7 @@ def test_clear(self): expected = show_vxlan_counters_clear_output # remove the counters snapshot - show.run_command("tunnelstat -D") + show.run_command(['tunnelstat', '-D']) for line in expected: assert line in result.output @@ -97,7 +97,7 @@ def test_clear_interface(self): expected = show_vxlan_counters_clear_interface_output # remove the counters snapshot - show.run_command("tunnelstat -D") + show.run_command(['tunnelstat', '-D']) for line in expected: assert line in result.output