From 52cff2aa4412d3da78f4bb7cfe3018d427fb5d01 Mon Sep 17 00:00:00 2001 From: Joe LeVeque Date: Thu, 6 Apr 2017 11:26:53 -0700 Subject: [PATCH] [CLI]: Fix broken commands (#28) Fixed the following commands, as well as some other cleanup: - show version - show platform - show lldp table - show techsupport --- scripts/generate_dump | 2 +- scripts/lldpshow | 124 ++++++++++++++++++++++++++++++++++++++++++ setup.py | 3 +- sonic_cli/main.py | 49 +++++++++++++---- 4 files changed, 164 insertions(+), 14 deletions(-) create mode 100755 scripts/lldpshow diff --git a/scripts/generate_dump b/scripts/generate_dump index 2372f78b2f38..f375577bb256 100755 --- a/scripts/generate_dump +++ b/scripts/generate_dump @@ -353,7 +353,7 @@ usage() { cat < Ethernet1/51 BR :fortyGigE0/0 + Ethernet4 Ethernet1/51 BR :fortyGigE0/4 + Ethernet8 Ethernet1/51 BR :fortyGigE0/8 + Ethernet12 Ethernet1/51 BR :fortyGigE0/12 + ... ... ... ... ... + Ethernet124 Ethernet4/20/1 BR :fortyGigE0/124 + eth0 Ethernet1/25 BR Ethernet1/25 + ----------------------------------------------------- + Total entries displayed: 33 +""" + +from __future__ import print_function +import subprocess +import re +import sys +import xml.etree.ElementTree as ET +from tabulate import tabulate + +class Lldpshow(object): + def __init__(self): + self.lldpraw = None + self.lldpsum = {} + self.err = None + ### So far only find Router and Bridge two capabilities in lldpctl, so any other capacility types will be read as Other + ### if further capability type is supported like WLAN, can just add the tag definition here + self.ctags = {'Router': 'R', 'Bridge': 'B'} + + def get_info(self): + """ + use 'lldpctl -f xml' command to gather local lldp detailed information + """ + lldp_cmd = 'lldpctl -f xml' + p = subprocess.Popen(lldp_cmd, stdout=subprocess.PIPE, shell=True) + (output, err) = p.communicate() + ## Wait for end of command. Get return returncode ## + returncode = p.wait() + ### if no error, get the lldpctl result + if returncode == 0: + self.lldpraw = output + else: + self.err = err + + def parse_cap(self, capabs): + """ + capabilities that are turned on for each interface + """ + capability = "" + for cap in capabs: + if cap.attrib['enabled'] == 'on': + captype = cap.attrib['type'] + if captype in self.ctags.keys(): + capability += self.ctags[captype] + else: + capability += 'O' + return capability + + def parse_info(self): + """ + Parse the lldp detailed infomation into dict + """ + if self.lldpraw is not None: + neis = ET.fromstring(self.lldpraw) + intfs = neis.findall('interface') + for intf in intfs: + l_intf = intf.attrib['name'] + self.lldpsum[l_intf] = {} + chassis = intf.find('chassis') + capabs = chassis.findall('capability') + capab = self.parse_cap(capabs) + self.lldpsum[l_intf]['r_name'] = chassis.find('name').text + remote_port = intf.find('port') + self.lldpsum[l_intf]['r_portid'] = remote_port.find('id').text + rmt_desc = remote_port.find('descr') + if rmt_desc is not None: + self.lldpsum[l_intf]['r_portname'] = rmt_desc.text + else: + self.lldpsum[l_intf]['r_portname'] = '' + self.lldpsum[l_intf]['capability'] = capab + + def sort_sum(self, summary): + """ Sort the summary information in the way that is expected(natural string).""" + alphanum_key = lambda key: [re.findall('[A-Za-z]+',key) + [int(port_num) for port_num in re.findall('\d+',key)]] + return sorted(summary, key=alphanum_key) + + + def display_sum(self): + """ + print out summary result of lldp neighbors + """ + if self.lldpraw is not None: + lldpstatus = [] + print ('Capability codes: (R) Router, (B) Bridge, (O) Other') + header = ['LocalPort', 'RemoteDevice', 'RemotePortID', 'Capability', 'RemotePortDescr'] + sortedsum = self.sort_sum(self.lldpsum) + for key in sortedsum: + lldpstatus.append([ key, self.lldpsum[key]['r_name'], self.lldpsum[key]['r_portid'], self.lldpsum[key]['capability'], self.lldpsum[key]['r_portname']]) + print (tabulate(lldpstatus, header)) + print ('-'.rjust(50, '-')) + print ('Total entries displayed: ', len(self.lldpsum)) + elif self.err is not None: + print ('Error:',self.err) + +def main(): + try: + lldp = Lldpshow() + lldp.get_info() + lldp.parse_info() + lldp.display_sum() + except Exception as e: + print(e.message, file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/setup.py b/setup.py index b1b319047b33..5ed8d0be72e4 100644 --- a/setup.py +++ b/setup.py @@ -15,15 +15,16 @@ 'sonic_cli': ['aliases.ini'] }, scripts=[ + 'scripts/aclshow', 'scripts/boot_part', 'scripts/coredump-compress', 'scripts/decode-syseeprom', 'scripts/fast-reboot', 'scripts/generate_dump', + 'scripts/lldpshow', 'scripts/portstat', 'scripts/sfputil', 'scripts/teamshow', - 'scripts/aclshow', ], data_files=[ ('/etc/bash_completion.d', ['data/etc/bash_completion.d/show']) diff --git a/sonic_cli/main.py b/sonic_cli/main.py index 6d14b22cc69e..47d60205d7e2 100644 --- a/sonic_cli/main.py +++ b/sonic_cli/main.py @@ -44,10 +44,10 @@ class AliasedGroup(DefaultGroup): """ def get_command(self, ctx, cmd_name): + global _config # If we haven't instantiated our global config, do it now and load current config if _config is None: - global _config _config = Config() # Load our config file @@ -171,12 +171,12 @@ def portchannel(): # 'lldp' group #### # -@cli.group(cls=AliasedGroup, default_if_no_args=True) +@cli.group(cls=AliasedGroup, default_if_no_args=False) def lldp(): pass # Default 'lldp' command (called if no subcommands or their aliases were passed) -@lldp.command(default=True) +@lldp.command() @click.argument('interfacename', required=False) def neighbors(interfacename): """Show LLDP neighbors""" @@ -229,10 +229,28 @@ def summary(): # 'platform' group #### # -@cli.group(invoke_without_command=True) +@cli.group(cls=AliasedGroup, default_if_no_args=False) def platform(): + pass + +@platform.command() +def summary(): """Show hardware platform information""" - run_command("platform-detect") + click.echo("") + + PLATFORM_TEMPLATE_FILE = "/tmp/cli_platform.j2" + PLATFORM_TEMPLATE_CONTENTS = "Platform: {{ platform }}\n" \ + "HwSKU: {{ minigraph_hwsku }}\n" \ + "ASIC: {{ asic_type }}" + + # Create a temporary Jinja2 template file to use with sonic-cfggen + f = open(PLATFORM_TEMPLATE_FILE, 'w') + f.write(PLATFORM_TEMPLATE_CONTENTS) + f.close() + + command = "sonic-cfggen -m /etc/sonic/minigraph.xml -y /etc/sonic/sonic_version.yml -t {0}".format(PLATFORM_TEMPLATE_FILE) + p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + click.echo(p.stdout.read()) # 'syseeprom' subcommand #### @platform.command() @@ -275,19 +293,26 @@ def default(process, tail, follow): def version(): """Show version information""" click.echo("") - command = 'cat /etc/ssw/sysDescription' + + VERSION_TEMPLATE_FILE = "/tmp/cli_version.j2" + VERSION_TEMPLATE_CONTENTS = "SONiC Software Version: SONiC.{{ build_version }}\n" \ + "Distribution: Debian {{ debian_version }}\n" \ + "Kernel: {{ kernel_version }}" + + # Create a temporary Jinja2 template file to use with sonic-cfggen + f = open(VERSION_TEMPLATE_FILE, 'w') + f.write(VERSION_TEMPLATE_CONTENTS) + f.close() + + command = "sonic-cfggen -y /etc/sonic/sonic_version.yml -t {0}".format(VERSION_TEMPLATE_FILE) p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) click.echo(p.stdout.read()) - click.echo("") + click.echo("Docker images:") command = 'docker images --format "table {{.Repository}}\\t{{.Tag}}\\t{{.ID}}\\t{{.Size}}"' p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) click.echo(p.stdout.read()) - command = 'uptime -p' - p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - click.echo("Kernel uptime " + p.stdout.read()) - # # 'environment' command ### @@ -333,7 +358,7 @@ def users(): @cli.command() def techsupport(): """Gather information for troubleshooting""" - run_command('acs_support -v') + run_command('generate_dump -v') #