diff --git a/main.py b/main.py index ef435c0..ebeb1ce 100644 --- a/main.py +++ b/main.py @@ -1,2457 +1,1893 @@ -#! /usr/bin/python -u - -import errno -import json -import netaddr -import netifaces -import os -import re -import subprocess -import sys - -import click -from click_default_group import DefaultGroup -from natsort import natsorted -from tabulate import tabulate - -import sonic_platform -from swsssdk import ConfigDBConnector -from swsssdk import SonicV2Connector - -import mlnx - -SONIC_CFGGEN_PATH = '/usr/local/bin/sonic-cfggen' - -try: - # noinspection PyPep8Naming - import ConfigParser as configparser -except ImportError: - # noinspection PyUnresolvedReferences - import configparser - - -# This is from the aliases example: -# https://github.com/pallets/click/blob/57c6f09611fc47ca80db0bd010f05998b3c0aa95/examples/aliases/aliases.py -class Config(object): - """Object to hold CLI config""" - - def __init__(self): - self.path = os.getcwd() - self.aliases = {} - - def read_config(self, filename): - parser = configparser.RawConfigParser() - parser.read([filename]) - try: - self.aliases.update(parser.items('aliases')) - except configparser.NoSectionError: - pass - -class InterfaceAliasConverter(object): - """Class which handles conversion between interface name and alias""" - - def __init__(self): - self.alias_max_length = 0 - - config_db = ConfigDBConnector() - config_db.connect() - self.port_dict = config_db.get_table('PORT') - - if not self.port_dict: - click.echo("port_dict is None!") - raise click.Abort() - - for port_name in self.port_dict.keys(): - try: - if self.alias_max_length < len( - self.port_dict[port_name]['alias']): - self.alias_max_length = len( - self.port_dict[port_name]['alias']) - except KeyError: - break - - def name_to_alias(self, interface_name): - """Return vendor interface alias if SONiC - interface name is given as argument - """ - if interface_name is not None: - for port_name in self.port_dict.keys(): - if interface_name == port_name: - return self.port_dict[port_name]['alias'] - - click.echo("Invalid interface {}".format(interface_name)) - raise click.Abort() - - def alias_to_name(self, interface_alias): - """Return SONiC interface name if vendor - port alias is given as argument - """ - if interface_alias is not None: - for port_name in self.port_dict.keys(): - if interface_alias == self.port_dict[port_name]['alias']: - return port_name - - click.echo("Invalid interface {}".format(interface_alias)) - raise click.Abort() - - -# Global Config object -_config = None - - -# This aliased group has been modified from click examples to inherit from DefaultGroup instead of click.Group. -# DefaultGroup is a superclass of click.Group which calls a default subcommand instead of showing -# a help message if no subcommand is passed -class AliasedGroup(DefaultGroup): - """This subclass of a DefaultGroup supports looking up aliases in a config - file and with a bit of magic. - """ - - 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: - _config = Config() - - # Load our config file - cfg_file = os.path.join(os.path.dirname(__file__), 'aliases.ini') - _config.read_config(cfg_file) - - # Try to get builtin commands as normal - rv = click.Group.get_command(self, ctx, cmd_name) - if rv is not None: - return rv - - # No builtin found. Look up an explicit command alias in the config - if cmd_name in _config.aliases: - actual_cmd = _config.aliases[cmd_name] - return click.Group.get_command(self, ctx, actual_cmd) - - # Alternative option: if we did not find an explicit alias we - # allow automatic abbreviation of the command. "status" for - # instance will match "st". We only allow that however if - # there is only one command. - matches = [x for x in self.list_commands(ctx) - if x.lower().startswith(cmd_name.lower())] - if not matches: - # No command name matched. Issue Default command. - ctx.arg0 = cmd_name - cmd_name = self.default_cmd_name - return DefaultGroup.get_command(self, ctx, cmd_name) - elif len(matches) == 1: - return DefaultGroup.get_command(self, ctx, matches[0]) - ctx.fail('Too many matches: %s' % ', '.join(sorted(matches))) - - -# To be enhanced. Routing-stack information should be collected from a global -# location (configdb?), so that we prevent the continous execution of this -# bash oneliner. To be revisited once routing-stack info is tracked somewhere. -def get_routing_stack(): - command = "sudo docker ps | grep bgp | awk '{print$2}' | cut -d'-' -f3 | cut -d':' -f1" - - try: - proc = subprocess.Popen(command, - stdout=subprocess.PIPE, - shell=True, - stderr=subprocess.STDOUT) - stdout = proc.communicate()[0] - proc.wait() - result = stdout.rstrip('\n') - - except OSError, e: - raise OSError("Cannot detect routing-stack") - - return (result) - - -# Global Routing-Stack variable -routing_stack = get_routing_stack() - - -def run_command(command, display_cmd=False): - 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 get_interface_mode() == "alias" and not command.startswith("intfutil"): - run_command_in_alias_mode(command) - raise sys.exit(0) - - proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - - while True: - output = proc.stdout.readline() - if output == "" and proc.poll() is not None: - break - if output: - click.echo(output.rstrip('\n')) - - rc = proc.poll() - if rc != 0: - sys.exit(rc) - - -def get_interface_mode(): - mode = os.getenv('SONIC_CLI_IFACE_MODE') - if mode is None: - mode = "default" - return mode - - -# Global class instance for SONiC interface name to alias conversion -iface_alias_converter = InterfaceAliasConverter() - - -def print_output_in_alias_mode(output, index): - """Convert and print all instances of SONiC interface - name to vendor-sepecific interface aliases. - """ - - alias_name = "" - interface_name = "" - - # Adjust tabulation width to length of alias name - if output.startswith("---"): - word = output.split() - dword = word[index] - underline = dword.rjust(iface_alias_converter.alias_max_length, - '-') - word[index] = underline - output = ' ' .join(word) - - # Replace SONiC interface name with vendor alias - word = output.split() - if word: - interface_name = word[index] - interface_name = interface_name.replace(':', '') - for port_name in natsorted(iface_alias_converter.port_dict.keys()): - if interface_name == port_name: - alias_name = iface_alias_converter.port_dict[port_name]['alias'] - if alias_name: - if len(alias_name) < iface_alias_converter.alias_max_length: - alias_name = alias_name.rjust( - iface_alias_converter.alias_max_length) - output = output.replace(interface_name, alias_name, 1) - - click.echo(output.rstrip('\n')) - - -def run_command_in_alias_mode(command): - """Run command and replace all instances of SONiC interface names - in output with vendor-sepecific interface aliases. - """ - - process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - - while True: - output = process.stdout.readline() - if output == '' and process.poll() is not None: - break - - if output: - index = 1 - raw_output = output - output = output.lstrip() - - if command.startswith("portstat"): - """Show interface counters""" - index = 0 - if output.startswith("IFACE"): - output = output.replace("IFACE", "IFACE".rjust( - iface_alias_converter.alias_max_length)) - print_output_in_alias_mode(output, index) - - elif command == "pfcstat": - """Show pfc counters""" - index = 0 - if output.startswith("Port Tx"): - output = output.replace("Port Tx", "Port Tx".rjust( - iface_alias_converter.alias_max_length)) - - elif output.startswith("Port Rx"): - output = output.replace("Port Rx", "Port Rx".rjust( - iface_alias_converter.alias_max_length)) - print_output_in_alias_mode(output, index) - - elif (command.startswith("sudo sfputil show eeprom")): - """show interface transceiver eeprom""" - index = 0 - print_output_in_alias_mode(raw_output, index) - - elif (command.startswith("sudo sfputil show")): - """show interface transceiver lpmode, - presence - """ - index = 0 - if output.startswith("Port"): - output = output.replace("Port", "Port".rjust( - iface_alias_converter.alias_max_length)) - print_output_in_alias_mode(output, index) - - elif command == "sudo lldpshow": - """show lldp table""" - index = 0 - if output.startswith("LocalPort"): - output = output.replace("LocalPort", "LocalPort".rjust( - iface_alias_converter.alias_max_length)) - print_output_in_alias_mode(output, index) - - elif command.startswith("queuestat"): - """show queue counters""" - index = 0 - if output.startswith("Port"): - output = output.replace("Port", "Port".rjust( - iface_alias_converter.alias_max_length)) - print_output_in_alias_mode(output, index) - - elif command == "fdbshow": - """show mac""" - index = 3 - if output.startswith("No."): - output = " " + output - output = re.sub( - 'Type', ' Type', output) - elif output[0].isdigit(): - output = " " + output - print_output_in_alias_mode(output, index) - elif command.startswith("nbrshow"): - """show arp""" - index = 2 - if "Vlan" in output: - output = output.replace('Vlan', ' Vlan') - print_output_in_alias_mode(output, index) - - else: - if index: - for port_name in iface_alias_converter.port_dict.keys(): - regex = re.compile(r"\b{}\b".format(port_name)) - result = re.findall(regex, raw_output) - if result: - interface_name = ''.join(result) - if not raw_output.startswith(" PortID:"): - raw_output = raw_output.replace( - interface_name, - iface_alias_converter.name_to_alias( - interface_name)) - click.echo(raw_output.rstrip('\n')) - - rc = process.poll() - if rc != 0: - sys.exit(rc) - - -CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help', '-?']) - -# -# 'cli' group (root group) -# - -# This is our entrypoint - the main "show" command -# TODO: Consider changing function name to 'show' for better understandability -@click.group(cls=AliasedGroup, context_settings=CONTEXT_SETTINGS) -def cli(): - """SONiC command line - 'show' command""" - pass - - -# -# 'arp' command ("show arp") -# - -@cli.command() -@click.argument('ipaddress', required=False) -@click.option('-if', '--iface') -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def arp(ipaddress, iface, verbose): - """Show IP ARP table""" - cmd = "nbrshow -4" - - if ipaddress is not None: - cmd += " -ip {}".format(ipaddress) - - if iface is not None: - if get_interface_mode() == "alias": - if not ((iface.startswith("PortChannel")) or - (iface.startswith("eth"))): - iface = iface_alias_converter.alias_to_name(iface) - - cmd += " -if {}".format(iface) - - run_command(cmd, display_cmd=verbose) - -# -# 'ndp' command ("show ndp") -# - -@cli.command() -@click.argument('ip6address', required=False) -@click.option('-if', '--iface') -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def ndp(ip6address, iface, verbose): - """Show IPv6 Neighbour table""" - cmd = "nbrshow -6" - - if ip6address is not None: - cmd += " -ip {}".format(ip6address) - - if iface is not None: - cmd += " -if {}".format(iface) - - run_command(cmd, display_cmd=verbose) - -# -# 'interfaces' group ("show interfaces ...") -# - -@cli.group(cls=AliasedGroup, default_if_no_args=False) -def interfaces(): - """Show details of the network interfaces""" - pass - -# -# 'neighbor' group ### -# -@interfaces.group(cls=AliasedGroup, default_if_no_args=False) -def neighbor(): - """Show neighbor related information""" - pass - -# 'expected' subcommand ("show interface neighbor expected") -@neighbor.command() -@click.argument('interfacename', required=False) -def expected(interfacename): - """Show expected neighbor information by interfaces""" - neighbor_cmd = 'sonic-cfggen -d --var-json "DEVICE_NEIGHBOR"' - p1 = subprocess.Popen(neighbor_cmd, shell=True, stdout=subprocess.PIPE) - neighbor_dict = json.loads(p1.stdout.read()) - - neighbor_metadata_cmd = 'sonic-cfggen -d --var-json "DEVICE_NEIGHBOR_METADATA"' - p2 = subprocess.Popen(neighbor_metadata_cmd, shell=True, stdout=subprocess.PIPE) - neighbor_metadata_dict = json.loads(p2.stdout.read()) - - #Swap Key and Value from interface: name to name: interface - device2interface_dict = {} - for port in natsorted(neighbor_dict.keys()): - device2interface_dict[neighbor_dict[port]['name']] = {'localPort': port, 'neighborPort': neighbor_dict[port]['port']} - - header = ['LocalPort', 'Neighbor', 'NeighborPort', 'NeighborLoopback', 'NeighborMgmt', 'NeighborType'] - body = [] - if interfacename: - for device in natsorted(neighbor_metadata_dict.keys()): - if device2interface_dict[device]['localPort'] == interfacename: - body.append([device2interface_dict[device]['localPort'], - device, - device2interface_dict[device]['neighborPort'], - neighbor_metadata_dict[device]['lo_addr'], - neighbor_metadata_dict[device]['mgmt_addr'], - neighbor_metadata_dict[device]['type']]) - else: - for device in natsorted(neighbor_metadata_dict.keys()): - body.append([device2interface_dict[device]['localPort'], - device, - device2interface_dict[device]['neighborPort'], - neighbor_metadata_dict[device]['lo_addr'], - neighbor_metadata_dict[device]['mgmt_addr'], - neighbor_metadata_dict[device]['type']]) - - click.echo(tabulate(body, header)) - - -@interfaces.group(cls=AliasedGroup, default_if_no_args=False) -def transceiver(): - """Show SFP Transceiver information""" - pass - - -@transceiver.command() -@click.argument('interfacename', required=False) -@click.option('-d', '--dom', 'dump_dom', is_flag=True, help="Also display Digital Optical Monitoring (DOM) data") -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def eeprom(interfacename, dump_dom, verbose): - """Show interface transceiver EEPROM information""" - - cmd = "sudo sfputil show eeprom" - - if dump_dom: - cmd += " --dom" - - if interfacename is not None: - if get_interface_mode() == "alias": - interfacename = iface_alias_converter.alias_to_name(interfacename) - - cmd += " -p {}".format(interfacename) - - run_command(cmd, display_cmd=verbose) - - -@transceiver.command() -@click.argument('interfacename', required=False) -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def lpmode(interfacename, verbose): - """Show interface transceiver low-power mode status""" - - cmd = "sudo sfputil show lpmode" - - if interfacename is not None: - if get_interface_mode() == "alias": - interfacename = iface_alias_converter.alias_to_name(interfacename) - - cmd += " -p {}".format(interfacename) - - run_command(cmd, display_cmd=verbose) - -@transceiver.command() -@click.argument('interfacename', required=False) -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def presence(interfacename, verbose): - """Show interface transceiver presence""" - - cmd = "sudo sfputil show presence" - - if interfacename is not None: - if get_interface_mode() == "alias": - interfacename = iface_alias_converter.alias_to_name(interfacename) - - cmd += " -p {}".format(interfacename) - - run_command(cmd, display_cmd=verbose) - - -@interfaces.command() -@click.argument('interfacename', required=False) -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def description(interfacename, verbose): - """Show interface status, protocol and description""" - - cmd = "intfutil description" - - if interfacename is not None: - if get_interface_mode() == "alias": - interfacename = iface_alias_converter.alias_to_name(interfacename) - - cmd += " {}".format(interfacename) - - run_command(cmd, display_cmd=verbose) - - -@interfaces.command() -@click.argument('interfacename', required=False) -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def status(interfacename, verbose): - """Show Interface status information""" - - cmd = "intfutil status" - - if interfacename is not None: - if get_interface_mode() == "alias": - interfacename = iface_alias_converter.alias_to_name(interfacename) - - cmd += " {}".format(interfacename) - - run_command(cmd, display_cmd=verbose) - - -# 'counters' subcommand ("show interfaces counters") -@interfaces.command() -@click.option('-a', '--printall', is_flag=True) -@click.option('-c', '--clear', is_flag=True) -@click.option('-p', '--period') -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def counters(period, printall, clear, verbose): - """Show interface counters""" - - cmd = "portstat" - - if clear: - cmd += " -c" - else: - if printall: - cmd += " -a" - if period is not None: - cmd += " -p {}".format(period) - - run_command(cmd, display_cmd=verbose) - -# 'portchannel' subcommand ("show interfaces portchannel") -@interfaces.command() -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def portchannel(verbose): - """Show PortChannel information""" - cmd = "teamshow" - run_command(cmd, display_cmd=verbose) - -# -# 'pfc' group ("show pfc ...") -# - -@cli.group(cls=AliasedGroup, default_if_no_args=False) -def pfc(): - """Show details of the priority-flow-control (pfc) """ - pass - -# 'counters' subcommand ("show interfaces pfccounters") -@pfc.command() -@click.option('-c', '--clear', is_flag=True) -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def counters(clear, verbose): - """Show pfc counters""" - - cmd = "pfcstat" - - if clear: - cmd += " -c" - - run_command(cmd, display_cmd=verbose) - -# 'naming_mode' subcommand ("show interfaces naming_mode") -@interfaces.command() -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def naming_mode(verbose): - """Show interface naming_mode status""" - - click.echo(get_interface_mode()) - - -# -# 'watermark' group ("show watermark telemetry interval") -# - -@cli.group(cls=AliasedGroup, default_if_no_args=False) -def watermark(): - """Show details of watermark """ - pass - -@watermark.group() -def telemetry(): - """Show watermark telemetry info""" - pass - -@telemetry.command('interval') -def show_tm_interval(): - """Show telemetry interval""" - command = 'watermarkcfg --show-interval' - run_command(command) - - -# -# 'queue' group ("show queue ...") -# - -@cli.group(cls=AliasedGroup, default_if_no_args=False) -def queue(): - """Show details of the queues """ - pass - -# 'queuecounters' subcommand ("show queue counters") -@queue.command() -@click.argument('interfacename', required=False) -@click.option('-c', '--clear', is_flag=True) -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def counters(interfacename, clear, verbose): - """Show queue counters""" - - cmd = "queuestat" - - if interfacename is not None: - if get_interface_mode() == "alias": - interfacename = iface_alias_converter.alias_to_name(interfacename) - - if clear: - cmd += " -c" - else: - if interfacename is not None: - cmd += " -p {}".format(interfacename) - - run_command(cmd, display_cmd=verbose) - -# watermarks subcommands ("show queue watermarks|persistent-watermarks") - -@queue.group() -def watermark(): - """Show queue user WM""" - pass - -@watermark.command('unicast') -def wm_q_uni(): - """Show user WM for unicast queues""" - command = 'watermarkstat -t q_shared_uni' - run_command(command) - -@watermark.command('multicast') -def wm_q_multi(): - """Show user WM for multicast queues""" - command = 'watermarkstat -t q_shared_multi' - run_command(command) - -@queue.group(name='persistent-watermark') -def persistent_watermark(): - """Show queue persistent WM""" - pass - -@persistent_watermark.command('unicast') -def pwm_q_uni(): - """Show persistent WM for persistent queues""" - command = 'watermarkstat -p -t q_shared_uni' - run_command(command) - -@persistent_watermark.command('multicast') -def pwm_q_multi(): - """Show persistent WM for multicast queues""" - command = 'watermarkstat -p -t q_shared_multi' - run_command(command) - - -# -# 'priority-group' group ("show priority-group ...") -# - -@cli.group(name='priority-group', cls=AliasedGroup, default_if_no_args=False) -def priority_group(): - """Show details of the PGs """ - - -@priority_group.group() -def watermark(): - """Show priority_group user WM""" - pass - -@watermark.command('headroom') -def wm_pg_headroom(): - """Show user headroom WM for pg""" - 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' - run_command(command) - -@priority_group.group(name='persistent-watermark') -def persistent_watermark(): - """Show queue persistent WM""" - pass - -@persistent_watermark.command('headroom') -def pwm_pg_headroom(): - """Show persistent headroom WM for pg""" - 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' - run_command(command) - - -# -# 'mac' command ("show mac ...") -# - -@cli.command() -@click.option('-v', '--vlan') -@click.option('-p', '--port') -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def mac(vlan, port, verbose): - """Show MAC (FDB) entries""" - - cmd = "fdbshow" - - if vlan is not None: - cmd += " -v {}".format(vlan) - - if port is not None: - cmd += " -p {}".format(port) - - run_command(cmd, display_cmd=verbose) - -# -# 'show route-map' command ("show route-map") -# - -@cli.command('route-map') -@click.argument('route_map_name', required=False) -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def route_map(route_map_name, verbose): - """show route-map""" - cmd = 'sudo vtysh -c "show route-map' - if route_map_name is not None: - cmd += ' {}'.format(route_map_name) - cmd += '"' - run_command(cmd, display_cmd=verbose) - -# -# 'ip' group ("show ip ...") -# - -# This group houses IP (i.e., IPv4) commands and subgroups -@cli.group(cls=AliasedGroup, default_if_no_args=False) -def ip(): - """Show IP (IPv4) commands""" - pass - - -# -# get_if_admin_state -# -# Given an interface name, return its admin state reported by the kernel. -# -def get_if_admin_state(iface): - admin_file = "/sys/class/net/{0}/flags" - - try: - state_file = open(admin_file.format(iface), "r") - except IOError as e: - print "Error: unable to open file: %s" % str(e) - return "error" - - content = state_file.readline().rstrip() - flags = int(content, 16) - - if flags & 0x1: - return "up" - else: - return "down" - - -# -# get_if_oper_state -# -# Given an interface name, return its oper state reported by the kernel. -# -def get_if_oper_state(iface): - oper_file = "/sys/class/net/{0}/carrier" - - try: - state_file = open(oper_file.format(iface), "r") - except IOError as e: - print "Error: unable to open file: %s" % str(e) - return "error" - - oper_state = state_file.readline().rstrip() - if oper_state == "1": - return "up" - else: - return "down" - - -# -# 'show ip interfaces' command -# -# Display all interfaces with an IPv4 address and their admin/oper states. -# Addresses from all scopes are included. Interfaces with no addresses are -# excluded. -# -@ip.command() -def interfaces(): - """Show interfaces IPv4 address""" - header = ['Interface', 'IPv4 address/mask', 'Admin/Oper'] - data = [] - - interfaces = natsorted(netifaces.interfaces()) - - for iface in interfaces: - ipaddresses = netifaces.ifaddresses(iface) - - if netifaces.AF_INET in ipaddresses: - ifaddresses = [] - for ipaddr in ipaddresses[netifaces.AF_INET]: - netmask = netaddr.IPAddress(ipaddr['netmask']).netmask_bits() - ifaddresses.append(["", str(ipaddr['addr']) + "/" + str(netmask)]) - - if len(ifaddresses) > 0: - admin = get_if_admin_state(iface) - if admin == "up": - oper = get_if_oper_state(iface) - else: - oper = "down" - data.append([iface, ifaddresses[0][1], admin + "/" + oper]) - for ifaddr in ifaddresses[1:]: - data.append(["", ifaddr[1], ""]) - - print tabulate(data, header, tablefmt="simple", stralign='left', missingval="") - - -# -# 'route' subcommand ("show ip route") -# - -@ip.command() -@click.argument('ipaddress', required=False) -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def route(ipaddress, verbose): - """Show IP (IPv4) routing table""" - cmd = 'sudo vtysh -c "show ip route' - - if ipaddress is not None: - cmd += ' {}'.format(ipaddress) - - cmd += '"' - - run_command(cmd, display_cmd=verbose) - -# -# 'prefix-list' subcommand ("show ip prefix-list") -# - -@ip.command('prefix-list') -@click.argument('prefix_list_name', required=False) -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def prefix_list(prefix_list_name, verbose): - """show ip prefix-list""" - cmd = 'sudo vtysh -c "show ip prefix-list' - if prefix_list_name is not None: - cmd += ' {}'.format(prefix_list_name) - cmd += '"' - run_command(cmd, display_cmd=verbose) - - -# 'protocol' command -@ip.command() -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def protocol(verbose): - """Show IPv4 protocol information""" - cmd = 'sudo vtysh -c "show ip protocol"' - run_command(cmd, display_cmd=verbose) - - -# -# 'ipv6' group ("show ipv6 ...") -# - -# This group houses IPv6-related commands and subgroups -@cli.group(cls=AliasedGroup, default_if_no_args=False) -def ipv6(): - """Show IPv6 commands""" - pass - - -# -# 'show ipv6 interfaces' command -# -# Display all interfaces with an IPv6 address and their admin/oper states. -# Addresses from all scopes are included. Interfaces with no addresses are -# excluded. -# -@ipv6.command() -def interfaces(): - """Show interfaces IPv6 address""" - header = ['Interface', 'IPv6 address/mask', 'Admin/Oper'] - data = [] - - interfaces = natsorted(netifaces.interfaces()) - - for iface in interfaces: - ipaddresses = netifaces.ifaddresses(iface) - - if netifaces.AF_INET6 in ipaddresses: - ifaddresses = [] - for ipaddr in ipaddresses[netifaces.AF_INET6]: - netmask = ipaddr['netmask'].split('/', 1)[-1] - ifaddresses.append(["", str(ipaddr['addr']) + "/" + str(netmask)]) - - if len(ifaddresses) > 0: - admin = get_if_admin_state(iface) - if admin == "up": - oper = get_if_oper_state(iface) - else: - oper = "down" - data.append([iface, ifaddresses[0][1], admin + "/" + oper]) - for ifaddr in ifaddresses[1:]: - data.append(["", ifaddr[1], ""]) - - print tabulate(data, header, tablefmt="simple", stralign='left', missingval="") - - -# -# 'route' subcommand ("show ipv6 route") -# - -@ipv6.command() -@click.argument('ipaddress', required=False) -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def route(ipaddress, verbose): - """Show IPv6 routing table""" - cmd = 'sudo vtysh -c "show ipv6 route' - - if ipaddress is not None: - cmd += ' {}'.format(ipaddress) - - cmd += '"' - - run_command(cmd, display_cmd=verbose) - - -# 'protocol' command -@ipv6.command() -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def protocol(verbose): - """Show IPv6 protocol information""" - cmd = 'sudo vtysh -c "show ipv6 protocol"' - run_command(cmd, display_cmd=verbose) - - -# -# Inserting BGP functionality into cli's show parse-chain. -# BGP commands are determined by the routing-stack being elected. -# -if routing_stack == "quagga": - from .bgp_quagga_v4 import bgp - ip.add_command(bgp) - from .bgp_quagga_v6 import bgp - ipv6.add_command(bgp) -elif routing_stack == "frr": - @cli.command() - @click.argument('bgp_args', nargs = -1, required = False) - @click.option('--verbose', is_flag=True, help="Enable verbose output") - def bgp(bgp_args, verbose): - """BGP information""" - bgp_cmd = "show bgp" - for arg in bgp_args: - bgp_cmd += " " + str(arg) - cmd = 'sudo vtysh -c "{}"'.format(bgp_cmd) - run_command(cmd, display_cmd=verbose) - - -# -# 'lldp' group ("show lldp ...") -# - -@cli.group(cls=AliasedGroup, default_if_no_args=False) -def lldp(): - """LLDP (Link Layer Discovery Protocol) information""" - pass - -# Default 'lldp' command (called if no subcommands or their aliases were passed) -@lldp.command() -@click.argument('interfacename', required=False) -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def neighbors(interfacename, verbose): - """Show LLDP neighbors""" - cmd = "sudo lldpctl" - - if interfacename is not None: - if get_interface_mode() == "alias": - interfacename = iface_alias_converter.alias_to_name(interfacename) - - cmd += " {}".format(interfacename) - - run_command(cmd, display_cmd=verbose) - -# 'table' subcommand ("show lldp table") -@lldp.command() -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def table(verbose): - """Show LLDP neighbors in tabular format""" - cmd = "sudo lldpshow" - run_command(cmd, display_cmd=verbose) - -# -# 'platform' group ("show platform ...") -# - -@cli.group(cls=AliasedGroup, default_if_no_args=False) -def platform(): - """Show platform-specific hardware info""" - pass - -version_info = sonic_platform.get_sonic_version_info() -if (version_info and version_info.get('asic_type') == 'mellanox'): - platform.add_command(mlnx.mlnx) - -# 'summary' subcommand ("show platform summary") -@platform.command() -def summary(): - """Show hardware platform information""" - machine_info = sonic_platform.get_machine_info() - platform = sonic_platform.get_platform_info(machine_info) - - config_db = ConfigDBConnector() - config_db.connect() - data = config_db.get_table('DEVICE_METADATA') - - try: - hwsku = data['localhost']['hwsku'] - except KeyError: - hwsku = "Unknown" - - version_info = sonic_platform.get_sonic_version_info() - asic_type = version_info['asic_type'] - - click.echo("Platform: {}".format(platform)) - click.echo("HwSKU: {}".format(hwsku)) - click.echo("ASIC: {}".format(asic_type)) - -# 'syseeprom' subcommand ("show platform syseeprom") -@platform.command() -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def syseeprom(verbose): - """Show system EEPROM information""" - cmd = "sudo decode-syseeprom" - run_command(cmd, display_cmd=verbose) - -# 'psustatus' subcommand ("show platform psustatus") -@platform.command() -@click.option('-i', '--index', default=-1, type=int, help="the index of PSU") -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def psustatus(index, verbose): - """Show PSU status information""" - cmd = "sudo psuutil status" - - if index >= 0: - cmd += " -i {}".format(index) - - run_command(cmd, display_cmd=verbose) - -# -# 'logging' command ("show logging") -# - -@cli.command() -@click.argument('process', required=False) -@click.option('-l', '--lines') -@click.option('-f', '--follow', is_flag=True) -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def logging(process, lines, follow, verbose): - """Show system log""" - if follow: - cmd = "sudo tail -F /var/log/syslog" - run_command(cmd, display_cmd=verbose) - else: - if os.path.isfile("/var/log/syslog.1"): - cmd = "sudo cat /var/log/syslog.1 /var/log/syslog" - else: - cmd = "sudo cat /var/log/syslog" - - if process is not None: - cmd += " | grep '{}'".format(process) - - if lines is not None: - cmd += " | tail -{}".format(lines) - - run_command(cmd, display_cmd=verbose) - - -# -# 'version' command ("show version") -# - -@cli.command() -def version(): - """Show version information""" - version_info = sonic_platform.get_sonic_version_info() - - click.echo("SONiC Software Version: SONiC.{}".format(version_info['build_version'])) - click.echo("Distribution: Debian {}".format(version_info['debian_version'])) - click.echo("Kernel: {}".format(version_info['kernel_version'])) - click.echo("Build commit: {}".format(version_info['commit_id'])) - click.echo("Build date: {}".format(version_info['build_date'])) - click.echo("Built by: {}".format(version_info['built_by'])) - - click.echo("\nDocker images:") - cmd = 'sudo docker images --format "table {{.Repository}}\\t{{.Tag}}\\t{{.ID}}\\t{{.Size}}"' - p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) - click.echo(p.stdout.read()) - -# -# 'environment' command ("show environment") -# - -@cli.command() -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def environment(verbose): - """Show environmentals (voltages, fans, temps)""" - cmd = "sudo sensors" - run_command(cmd, display_cmd=verbose) - - -# -# 'processes' group ("show processes ...") -# - -@cli.group(cls=AliasedGroup, default_if_no_args=False) -def processes(): - """Display process information""" - pass - -@processes.command() -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def summary(verbose): - """Show processes info""" - # Run top batch mode to prevent unexpected newline after each newline - cmd = "ps -eo pid,ppid,cmd,%mem,%cpu " - run_command(cmd, display_cmd=verbose) - - -# 'cpu' subcommand ("show processes cpu") -@processes.command() -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def cpu(verbose): - """Show processes CPU info""" - # Run top in batch mode to prevent unexpected newline after each newline - cmd = "top -bn 1 -o %CPU" - run_command(cmd, display_cmd=verbose) - -# 'memory' subcommand -@processes.command() -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def memory(verbose): - """Show processes memory info""" - # Run top batch mode to prevent unexpected newline after each newline - cmd = "top -bn 1 -o %MEM" - run_command(cmd, display_cmd=verbose) - -# -# 'users' command ("show users") -# - -@cli.command() -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def users(verbose): - """Show users""" - cmd = "who" - run_command(cmd, display_cmd=verbose) - - -# -# 'techsupport' command ("show techsupport") -# - -@cli.command() -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def techsupport(verbose): - """Gather information for troubleshooting""" - cmd = "sudo generate_dump -v" - run_command(cmd, display_cmd=verbose) - - -# -# 'runningconfiguration' group ("show runningconfiguration") -# - -@cli.group(cls=AliasedGroup, default_if_no_args=False) -def runningconfiguration(): - """Show current running configuration information""" - pass - - -# 'all' subcommand ("show runningconfiguration all") -@runningconfiguration.command() -@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) - - -# 'bgp' subcommand ("show runningconfiguration bgp") -@runningconfiguration.command() -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def bgp(verbose): - """Show BGP running configuration""" - cmd = 'sudo vtysh -c "show running-config"' - run_command(cmd, display_cmd=verbose) - - -# 'interfaces' subcommand ("show runningconfiguration interfaces") -@runningconfiguration.command() -@click.argument('interfacename', required=False) -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def interfaces(interfacename, verbose): - """Show interfaces running configuration""" - cmd = "cat /etc/network/interfaces" - - if interfacename is not None: - cmd += " | grep {} -A 4".format(interfacename) - - run_command(cmd, display_cmd=verbose) - - -# 'snmp' subcommand ("show runningconfiguration snmp") -@runningconfiguration.command() -@click.argument('server', required=False) -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def snmp(server, verbose): - """Show SNMP information""" - cmd = "sudo docker exec snmp cat /etc/snmp/snmpd.conf" - - if server is not None: - cmd += " | grep -i agentAddress" - - run_command(cmd, display_cmd=verbose) - - -# 'ntp' subcommand ("show runningconfiguration ntp") -@runningconfiguration.command() -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def ntp(verbose): - """Show NTP running configuration""" - cmd = "cat /etc/ntp.conf" - run_command(cmd, display_cmd=verbose) - - -# -# 'startupconfiguration' group ("show startupconfiguration ...") -# - -@cli.group(cls=AliasedGroup, default_if_no_args=False) -def startupconfiguration(): - """Show startup configuration information""" - pass - - -# 'bgp' subcommand ("show startupconfiguration bgp") -@startupconfiguration.command() -@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) - result = proc.stdout.read().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) - elif result == "frr": - 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) - else: - click.echo("Unidentified routing-stack") - -# -# 'ntp' command ("show ntp") -# - -@cli.command() -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def ntp(verbose): - """Show NTP information""" - cmd = "ntpq -p -n" - run_command(cmd, display_cmd=verbose) - - -# -# 'uptime' command ("show uptime") -# - -@cli.command() -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def uptime(verbose): - """Show system uptime""" - 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" - 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" - run_command(cmd, display_cmd=verbose) - -@cli.group(cls=AliasedGroup, default_if_no_args=False) -def vlan(): - """Show VLAN information""" - pass - -@vlan.command() -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def brief(verbose): - """Show all bridge information""" - config_db = ConfigDBConnector() - config_db.connect() - header = ['VLAN ID', 'IP Address', 'Ports', 'Port Tagging', 'DHCP Helper Address'] - body = [] - vlan_keys = [] - - # Fetching data from config_db for VLAN, VLAN_INTERFACE and VLAN_MEMBER - vlan_dhcp_helper_data = config_db.get_table('VLAN') - vlan_ip_data = config_db.get_table('VLAN_INTERFACE') - vlan_ports_data = config_db.get_table('VLAN_MEMBER') - - vlan_keys = natsorted(vlan_dhcp_helper_data.keys()) - - # Defining dictionaries for DHCP Helper address, Interface Gateway IP, - # VLAN ports and port tagging - vlan_dhcp_helper_dict = {} - vlan_ip_dict = {} - vlan_ports_dict = {} - vlan_tagging_dict = {} - - # Parsing DHCP Helpers info - for key in natsorted(vlan_dhcp_helper_data.keys()): - try: - if vlan_dhcp_helper_data[key]['dhcp_servers']: - vlan_dhcp_helper_dict[str(key.strip('Vlan'))] = vlan_dhcp_helper_data[key]['dhcp_servers'] - except KeyError: - vlan_dhcp_helper_dict[str(key.strip('Vlan'))] = " " - pass - - # Parsing VLAN Gateway info - for key in natsorted(vlan_ip_data.keys()): - interface_key = str(key[0].strip("Vlan")) - interface_value = str(key[1]) - if interface_key in vlan_ip_dict: - vlan_ip_dict[interface_key].append(interface_value) - else: - vlan_ip_dict[interface_key] = [interface_value] - - # Parsing VLAN Ports info - for key in natsorted(vlan_ports_data.keys()): - ports_key = str(key[0].strip("Vlan")) - ports_value = str(key[1]) - ports_tagging = vlan_ports_data[key]['tagging_mode'] - if ports_key in vlan_ports_dict: - vlan_ports_dict[ports_key].append(ports_value) - else: - vlan_ports_dict[ports_key] = [ports_value] - if ports_key in vlan_tagging_dict: - vlan_tagging_dict[ports_key].append(ports_tagging) - else: - vlan_tagging_dict[ports_key] = [ports_tagging] - - # Printing the following dictionaries in tablular forms: - # vlan_dhcp_helper_dict={}, vlan_ip_dict = {}, vlan_ports_dict = {} - # vlan_tagging_dict = {} - for key in natsorted(vlan_dhcp_helper_dict.keys()): - if key not in vlan_ip_dict: - ip_address = "" - else: - ip_address = ','.replace(',', '\n').join(vlan_ip_dict[key]) - if key not in vlan_ports_dict: - vlan_ports = "" - else: - vlan_ports = ','.replace(',', '\n').join((vlan_ports_dict[key])) - if key not in vlan_dhcp_helper_dict: - dhcp_helpers = "" - else: - dhcp_helpers = ','.replace(',', '\n').join(vlan_dhcp_helper_dict[key]) - if key not in vlan_tagging_dict: - vlan_tagging = "" - else: - vlan_tagging = ','.replace(',', '\n').join((vlan_tagging_dict[key])) - body.append([key, ip_address, vlan_ports, vlan_tagging, dhcp_helpers]) - click.echo(tabulate(body, header, tablefmt="grid")) - -@vlan.command() -@click.option('-s', '--redis-unix-socket-path', help='unix socket path for redis connection') -def config(redis_unix_socket_path): - kwargs = {} - if redis_unix_socket_path: - kwargs['unix_socket_path'] = redis_unix_socket_path - config_db = ConfigDBConnector(**kwargs) - config_db.connect(wait_for_init=False) - data = config_db.get_table('VLAN') - keys = data.keys() - - def tablelize(keys, data): - table = [] - - for k in keys: - for m in data[k].get('members', []): - r = [] - r.append(k) - r.append(data[k]['vlanid']) - if get_interface_mode() == "alias": - alias = iface_alias_converter.name_to_alias(m) - r.append(alias) - else: - r.append(m) - - entry = config_db.get_entry('VLAN_MEMBER', (k, m)) - mode = entry.get('tagging_mode') - if mode == None: - r.append('?') - else: - r.append(mode) - - table.append(r) - - return table - - header = ['Name', 'VID', 'Member', 'Mode'] - click.echo(tabulate(tablelize(keys, data), header)) - -@cli.command('services') -def services(): - """Show all daemon services""" - cmd = "sudo docker ps --format '{{.Names}}'" - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=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) - print proc1.stdout.read() - else: - break - -@cli.command() -def aaa(): - """Show AAA configuration""" - config_db = ConfigDBConnector() - config_db.connect() - data = config_db.get_table('AAA') - output = '' - - aaa = { - 'authentication': { - 'login': 'local (default)', - 'failthrough': 'True (default)', - 'fallback': 'True (default)' - } - } - if 'authentication' in data: - aaa['authentication'].update(data['authentication']) - for row in aaa: - entry = aaa[row] - for key in entry: - output += ('AAA %s %s %s\n' % (row, key, str(entry[key]))) - click.echo(output) - - -@cli.command() -def tacacs(): - """Show TACACS+ configuration""" - config_db = ConfigDBConnector() - config_db.connect() - output = '' - data = config_db.get_table('TACPLUS') - - tacplus = { - 'global': { - 'auth_type': 'pap (default)', - 'timeout': '5 (default)', - 'passkey': ' (default)' - } - } - if 'global' in data: - tacplus['global'].update(data['global']) - for key in tacplus['global']: - output += ('TACPLUS global %s %s\n' % (str(key), str(tacplus['global'][key]))) - - data = config_db.get_table('TACPLUS_SERVER') - if data != {}: - for row in data: - entry = data[row] - output += ('\nTACPLUS_SERVER address %s\n' % row) - for key in entry: - output += (' %s %s\n' % (key, str(entry[key]))) - click.echo(output) - -# -# 'mirror_session' command ("show mirror_session ...") -# -@cli.command() -@click.argument('session_name', required=False) -@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" - - if session_name is not None: - cmd += " {}".format(session_name) - - run_command(cmd, display_cmd=verbose) - - -# -# 'acl' group ### -# - -@cli.group(cls=AliasedGroup, default_if_no_args=False) -def acl(): - """Show ACL related information""" - pass - - -# 'rule' subcommand ("show acl rule") -@acl.command() -@click.argument('table_name', required=False) -@click.argument('rule_id', required=False) -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def rule(table_name, rule_id, verbose): - """Show existing ACL rules""" - cmd = "acl-loader show rule" - - if table_name is not None: - cmd += " {}".format(table_name) - - if rule_id is not None: - cmd += " {}".format(rule_id) - - run_command(cmd, display_cmd=verbose) - - -# 'table' subcommand ("show acl table") -@acl.command() -@click.argument('table_name', required=False) -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def table(table_name, verbose): - """Show existing ACL tables""" - cmd = "acl-loader show table" - - if table_name is not None: - cmd += " {}".format(table_name) - - run_command(cmd, display_cmd=verbose) - - -# -# 'ecn' command ("show ecn") -# -@cli.command('ecn') -def ecn(): - """Show ECN configuration""" - cmd = "ecnconfig -l" - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) - click.echo(proc.stdout.read()) - - -# 'mmu' command ("show mmu") -# -@cli.command('mmu') -def mmu(): - """Show mmu configuration""" - cmd = "mmuconfig -l" - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) - click.echo(proc.stdout.read()) - - -# -# 'reboot-cause' command ("show reboot-cause") -# -@cli.command('reboot-cause') -def reboot_cause(): - """Show cause of most recent reboot""" - PREVIOUS_REBOOT_CAUSE_FILE = "/host/reboot-cause/previous-reboot-cause.txt" - - # At boot time, PREVIOUS_REBOOT_CAUSE_FILE is generated based on - # the contents of the 'reboot cause' file as it was left when the device - # went down for reboot. This file should always be created at boot, - # but check first just in case it's not present. - if not os.path.isfile(PREVIOUS_REBOOT_CAUSE_FILE): - click.echo("Unable to determine cause of previous reboot\n") - else: - cmd = "cat {}".format(PREVIOUS_REBOOT_CAUSE_FILE) - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) - click.echo(proc.stdout.read()) - - -# -# 'line' command ("show line") -# -@cli.command('line') -def line(): - """Show all /dev/ttyUSB lines and their info""" - cmd = "consutil show" - run_command(cmd, display_cmd=verbose) - return - - -@cli.group(cls=AliasedGroup, default_if_no_args=False) -def warm_restart(): - """Show warm restart configuration and state""" - pass - -@warm_restart.command() -@click.option('-s', '--redis-unix-socket-path', help='unix socket path for redis connection') -def state(redis_unix_socket_path): - """Show warm restart state""" - kwargs = {} - if redis_unix_socket_path: - kwargs['unix_socket_path'] = redis_unix_socket_path - - data = {} - db = SonicV2Connector(host='127.0.0.1') - db.connect(db.STATE_DB, False) # Make one attempt only - - TABLE_NAME_SEPARATOR = '|' - prefix = 'WARM_RESTART_TABLE' + TABLE_NAME_SEPARATOR - _hash = '{}{}'.format(prefix, '*') - table_keys = db.keys(db.STATE_DB, _hash) - - def remove_prefix(text, prefix): - if text.startswith(prefix): - return text[len(prefix):] - return text - - table = [] - for tk in table_keys: - entry = db.get_all(db.STATE_DB, tk) - r = [] - r.append(remove_prefix(tk, prefix)) - if 'restore_count' not in entry: - r.append("") - else: - r.append(entry['restore_count']) - - if 'state' not in entry: - r.append("") - else: - r.append(entry['state']) - - table.append(r) - - header = ['name', 'restore_count', 'state'] - click.echo(tabulate(table, header)) - -@warm_restart.command() -@click.option('-s', '--redis-unix-socket-path', help='unix socket path for redis connection') -def config(redis_unix_socket_path): - """Show warm restart config""" - kwargs = {} - if redis_unix_socket_path: - kwargs['unix_socket_path'] = redis_unix_socket_path - config_db = ConfigDBConnector(**kwargs) - config_db.connect(wait_for_init=False) - data = config_db.get_table('WARM_RESTART') - keys = data.keys() - - def tablelize(keys, data): - table = [] - - for k in keys: - r = [] - r.append(k) - - if 'enable' not in data[k]: - r.append("false") - else: - r.append(data[k]['enable']) - - if 'neighsyncd_timer' in data[k]: - r.append("neighsyncd_timer") - r.append(data[k]['neighsyncd_timer']) - elif 'bgp_timer' in data[k]: - r.append("bgp_timer") - r.append(data[k]['bgp_timer']) - elif 'teamsyncd_timer' in data[k]: - r.append("teamsyncd_timer") - r.append(data[k]['teamsyncd_timer']) - else: - r.append("NULL") - r.append("NULL") - - table.append(r) - - return table - - header = ['name', 'enable', 'timer_name', 'timer_duration'] - click.echo(tabulate(tablelize(keys, data), header)) - -# -#echo empty line -# -def echo_empty_line(): - click.echo("\n") - - -# -# function name : print_test_title -# description : This function used for all the SSD and PCIE test info are start with a uniformed format . -# Details of format as following: -# "--------------------------------------------------------------------------------" -# " testname " -# "--------------------------------------------------------------------------------" -# - -def print_test_title(testname): - click.echo("{0:-^80s}".format("-")) - click.echo("{name: ^80s}".format(name=testname)) - click.echo("{0:-^80s}".format("-")) - - - -# -# 'ssd_firmwareinfo' command ("show ssd_firmwareinfo /dev/xxx") -# use this command to show SSD firmware info -# - -@cli.command() -@click.argument("device") -def ssd_firmwareinfo(device): - """Show ssd fwinfo""" - - checkin = 0 - testname="SSD Firmwareinfo Test" - # get the SSD information by call the smartctl cmd - command = "sudo smartctl -i " + device - proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - output = proc.stdout.readlines() - (out, err) = proc.communicate() - - print_test_title(testname) - #smartctl cmd return failed - if proc.returncode > 0: - for line in output: - click.echo(line.strip()) - return - else: - for line in output: - if ("Model Family" in line) or ("Device Model" in line) or ("Firmware Version" in line): - click.echo(line.strip()) - checkin = 1 - - if (checkin == 0): - click.echo("Can't get Firmwareinfo") - - echo_empty_line() - - -# -# 'ssd_capcity' command ("show ssd_capcity /dev/xxx") -# use this command to show SSD capcity info -# - -@cli.command() -@click.argument("device") -def ssd_capcity(device): - """Show ssd capcity""" - - checkin = 0 - testname = "SSD Capcity Test" - command = "sudo smartctl -i " + device - proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - output = proc.stdout.readlines() - (out, err) = proc.communicate() - - print_test_title(testname) - #smartctl cmd return failed - if proc.returncode > 0: - for line in output: - click.echo(line.strip()) - return - else: - for line in output: - if "User Capacity" in line: - click.echo(line.strip()) - checkin = 1 - - if(checkin == 0): - click.echo("Can't get SSD Capcity") - - echo_empty_line() - -# -# 'ssd_sn' command ("show ssd_sn /dev/xxx") -# use this command to show SSD serial number -# - -@cli.command() -@click.argument("device") -def ssd_sn(device): - """Show ssd serial number""" - - checkin = 0 - testname = "SSD SN Test" - command = "sudo smartctl -i " + device - proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - output = proc.stdout.readlines() - (out, err) = proc.communicate() - - print_test_title(testname) - #smartctl cmd return failed - if proc.returncode > 0: - for line in output: - click.echo(line.strip()) - return - else: - for line in output: - if "Serial Number" in line: - click.echo(line.strip()) - checkin = 1 - - if (checkin == 0): - click.echo("Can't get SSD serial number") - - echo_empty_line() - -# -# 'ssd_remainTime' command ("show ssd_remainTime /dev/xxx") -# use this command to show SSD Remaining Time -# - -@cli.command() -@click.argument("device") -def ssd_remainTime(device): - """Show ssd Remaining Time""" - - checkin = 0 - testname = "SSD RemainTime Test" - command = "sudo smartctl -A " + device - proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - output = proc.stdout.readlines() - (out, err) = proc.communicate() - - print_test_title(testname) - #smartctl cmd return failed - if proc.returncode > 0: - for line in output: - click.echo(line.strip()) - return - else: - for line in output: - if "Power_On_Hours" in line: - rawval = line.split()[-1] - poweron = int(rawval) - checkin = 1 - - if(checkin == 1): - # remainTime = Power on Hours * (100/7.26) – 1 (xPower on Hours) - remainingtime = poweron * 100 / 7.62 - poweron - click.echo("Remaning Time: {0:.2f} Hours".format(remainingtime)) - else: - click.echo("Can't get 'Power_On_Hours' attributes") - - echo_empty_line() - - -# -# 'ssd_peCycle' command ("show ssd_peCycle /dev/xxx") -# use this command to show SSD P/E cycle -# - -@cli.command() -@click.argument("device") -def ssd_peCycle(device): - """Show ssd P/E cycle""" - - checkin = 0 - testname = "SSD P/E Cycle Test" - command = "sudo smartctl -i " + device - proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - output = proc.stdout.readlines() - (out, err) = proc.communicate() - - print_test_title(testname) - #smartctl cmd return failed - if proc.returncode > 0: - for line in output: - click.echo(line.strip()) - return - else: - for line in output: - if "Device Model" in line: - checkin = 1 - if ("3ME3" in line) or ("3ME4" in line): - cycle = 3000 - elif ("3IE3" in line): - cycle = 20000 - else: - checkin = 0 - click.echo(line.strip()) - click.echo("Device Model Not Match 3ME3 3ME4 or 3IE3") - return - - if (checkin == 1): - click.echo("Device P/E Cycle: {0}".format(cycle)) - else: - click.echo("Can't get device model") - - echo_empty_line() - - -# -# 'ssd_health' command ("show ssd_health /dev/xxx") -# use this command to show SSD health status -# - -@cli.command() -@click.argument("device") -def ssd_health(device): - """Check the health status""" - - checkin = 0 - testname = "SSD Health Test" - command = "sudo smartctl -i " + device - proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - output = proc.stdout.readlines() - (out, err) = proc.communicate() - - print_test_title(testname) - #smartctl cmd return failed - if proc.returncode > 0: - for line in output: - click.echo(line.strip()) - return - else: - for line in output: - if "Device Model" in line: - checkin = 1 - if ("3ME3" in line) or ("3ME4" in line): - cycle = 3000 - elif ("3IE3" in line): - cycle = 20000 - else: - checkin = 0 - click.echo(line.strip()) - click.echo("Device Model Not Match 3ME3 3ME4 or 3IE3") - return - - if (checkin == 0): - click.echo("Can't get device model") - return - elif (checkin == 1): - checkin = 0 - command = "sudo smartctl -A " + device - proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - output = proc.stdout.readlines() - (out, err) = proc.communicate() - - # smartctl cmd return failed - if proc.returncode > 0: - for line in output: - click.echo(line.strip()) - return - else: - for line in output: - if "Average_Erase_Count" in line: - rawval = line.split()[-1] - avgerase = int(rawval) - checkin = 1 - - if (checkin == 1): - #health pre = (P/E cycle - AVG erese)/ P/E cycle - healthpre = (cycle - avgerase) * 100.0 / cycle - click.echo("Device Health Status Is: {0:.2f}%".format(healthpre)) - else: - click.echo("Can't get Average_Erase_Count attributes") - - echo_empty_line() - - -# -# 'ssd_badblock' command ("show ssd_badblock /dev/xxx") -# use this command to Check the later bad block status -# - -@cli.command() -@click.argument("device") -def ssd_badblock(device): - """Check the later bad block status which may created""" - - checkin = 0 - testname = "SSD Badblock Test" - command = "sudo smartctl -A " + device - proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - output = proc.stdout.readlines() - (out, err) = proc.communicate() - - print_test_title(testname) - #smartctl cmd return failed - if proc.returncode > 0: - for line in output: - click.echo(line.strip()) - return - else: - for line in output: - if "Later_Bad_Block" in line: - rawval = line.split()[-1] - click.echo("Later_Bad_Block: {0}".format(rawval)) - checkin += 1 - - if "Later_Bad_Blk_Inf_R/W/E" in line: - rawval_read = line.split()[-3] - click.echo("Later_Bad_Blk_Inf Read: {0}".format(rawval)) - rawval_write = line.split()[-2] - click.echo("Later_Bad_Blk_Inf Write: {0}".format(rawval)) - rawval_erase = line.split()[-1] - click.echo("Later_Bad_Blk_Inf Erase: {0}".format(rawval)) - checkin += 1 - - if(checkin != 2): - click.echo("Can't get all 'Later_Bad_Block' attributes") - - echo_empty_line() - - -# -# 'ssd_temperature' command ("show ssd_temperature /dev/xxx") -# use this command to show SSD temperature -# - -@cli.command() -@click.argument("device") -def ssd_temperature(device): - """Read SSD temperature °C""" - - checkin = 0 - testname = "SSD Temperature Test" - command = "sudo smartctl -A " + device - proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - output = proc.stdout.readlines() - (out, err) = proc.communicate() - - print_test_title(testname) - #smartctl cmd return failed - if proc.returncode > 0: - for line in output: - click.echo(line.strip()) - return - else: - for line in output: - if "Temperature_Celsius" in line: - rawval = line.split()[9] - temperature = int(rawval) - checkin = 1 - - if(checkin == 1): - click.echo("Temperature_Celsius: {0}°C".format(temperature)) - else: - click.echo("Can't get 'Temperature_Celsius' attributes") - - echo_empty_line() - - -# -# 'ssd_all' command -# execute all test iterms of SSD -# - -@cli.command() -@click.argument("device") -def ssd_all(device): - """Execute all test iterms of SSD""" - - command = "sudo smartctl -i " + device - procinfo = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - outputinfo = procinfo.stdout.readlines() - (out, err) = procinfo.communicate() - #smartctl cmd return failed - if procinfo.returncode > 0: - for line in outputinfo: - click.echo(line.strip()) - return - - command = "sudo smartctl -A " + device - procattr = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - outputattr = procattr.stdout.readlines() - (out, err) = procattr.communicate() - #smartctl cmd return failed - if procattr.returncode > 0: - for line in outputattr: - click.echo(line.strip()) - return - - #"""Show ssd fwinfo""" - checkin = 0 - testname = "SSD Firmwareinfo Test" - print_test_title(testname) - for line in outputinfo: - if ("Model Family" in line) or ("Device Model" in line) or ("Firmware Version" in line): - click.echo(line.strip()) - checkin = 1 - - if (checkin == 0): - click.echo("Can't get Firmwareinfo") - - echo_empty_line() - - #"""Show ssd capcity""" - checkin = 0 - testname = "SSD Capcity Test" - - print_test_title(testname) - for line in outputinfo: - if "User Capacity" in line: - click.echo(line.strip()) - checkin = 1 - - if(checkin == 0): - click.echo("Can't get SSD Capcity") - - echo_empty_line() - - #"""Show ssd serial number""" - checkin = 0 - testname = "SSD SN Test" - print_test_title(testname) - for line in outputinfo: - if "Serial Number" in line: - click.echo(line.strip()) - checkin = 1 - - if (checkin == 0): - click.echo("Can't get SSD serial number") - - echo_empty_line() - - #"""Show ssd Remaining Time""" - checkin = 0 - testname = "SSD RemainTime Test" - print_test_title(testname) - for line in outputattr: - if "Power_On_Hours" in line: - rawval = line.split()[-1] - poweron = int(rawval) - checkin = 1 - - if(checkin == 1): - # remainTime = Power on Hours * (100/7.26) – 1 (xPower on Hours) - remainingtime = poweron * 100 / 7.62 - poweron - click.echo("Remaning Time: {0:.2f} Hours".format(remainingtime)) - else: - click.echo("Can't get 'Power_On_Hours' attributes") - - echo_empty_line() - - #"""Show ssd P/E cycle""" - checkin = 0 - testname = "SSD P/E Cycle Test" - - print_test_title(testname) - for line in outputinfo: - if "Device Model" in line: - checkin = 1 - if ("3ME3" in line) or ("3ME4" in line): - cycle = 3000 - elif ("3IE3" in line): - cycle = 20000 - else: - checkin = 0 - click.echo(line.strip()) - click.echo("Device Model Not Match 3ME3 3ME4 or 3IE3") - return - - if (checkin == 1): - click.echo("Device P/E Cycle: {0}".format(cycle)) - else: - click.echo("Can't get device model") - - echo_empty_line() - - #"""Check the health status""" - checkin = 0 - testname = "SSD Health Test" - - print_test_title(testname) - for line in outputinfo: - if "Device Model" in line: - checkin = 1 - if ("3ME3" in line) or ("3ME4" in line): - cycle = 3000 - elif ("3IE3" in line): - cycle = 20000 - else: - checkin = 0 - click.echo(line.strip()) - click.echo("Device Model Not Match 3ME3 3ME4 or 3IE3") - - if (checkin == 0): - click.echo("Can't get device model") - elif (checkin == 1): - checkin = 0 - for line in outputattr: - if "Average_Erase_Count" in line: - rawval = line.split()[-1] - avgerase = int(rawval) - checkin = 1 - - if (checkin == 1): - #health pre = (P/E cycle - AVG erese)/ P/E cycle - healthpre = (cycle - avgerase) * 100.0 / cycle - click.echo("Device Health Status Is: {0:.2f}%".format(healthpre)) - else: - click.echo("Can't get Average_Erase_Count attributes") - - echo_empty_line() - - #"""Check the later bad block status which may created""" - checkin = 0 - testname = "SSD Badblock Test" - print_test_title(testname) - for line in outputattr: - if "Later_Bad_Block" in line: - rawval = line.split()[-1] - click.echo("Later_Bad_Block: {0}".format(rawval)) - checkin += 1 - - if "Later_Bad_Blk_Inf_R/W/E" in line: - rawval_read = line.split()[-3] - click.echo("Later_Bad_Blk_Inf Read: {0}".format(rawval)) - rawval_write = line.split()[-2] - click.echo("Later_Bad_Blk_Inf Write: {0}".format(rawval)) - rawval_erase = line.split()[-1] - click.echo("Later_Bad_Blk_Inf Erase: {0}".format(rawval)) - checkin += 1 - - if (checkin != 2): - click.echo("Can't get all 'Later_Bad_Block' attributes") - - echo_empty_line() - - #"""Read SSD temperature range 0~70 °C""" - checkin = 0 - testname = "SSD Temperature Test" - print_test_title(testname) - for line in outputattr: - if "Temperature_Celsius" in line: - rawval = line.split()[9] - temperature = int(rawval) - checkin = 1 - - if(checkin == 1): - click.echo("Temperature_Celsius: {0}°C".format(temperature)) - else: - click.echo("Can't get 'Temperature_Celsius' attributes") - - echo_empty_line() - - -# -# 'ssd-help' command #### -# - -@cli.command() -def ssd_help(): - """Show all SSD cmd""" - - click.echo("\nUsage: show [options] [device]") - click.echo("========================= SHOW INFORMATION OPTIONS =========================\n") - click.echo("{0:30s}{1:<40}".format(" ssd_firmwareinfo", "show SSD firmware info")) - click.echo("{0:30s}{1:<40}".format(" ssd_capcity ", "show SSD capcity")) - click.echo("{0:30s}{1:<40}".format(" ssd_sn ", "show SSD serial number")) - click.echo("{0:30s}{1:<40}".format(" ssd_remaintime ", "show SSD remaining time")) - click.echo("{0:30s}{1:<40}".format(" ssd_pecycle ", "show SSD P/E cycle")) - click.echo("{0:30s}{1:<40}".format(" ssd_health ", "check the health status which should be > 95% after MFG test")) - click.echo("{0:30s}{1:<40}".format(" ssd_badblock ", "check the later bad block status which may created during test")) - click.echo("{0:30s}{1:<40}".format(" ssd_temperature ", "read SSD temperature; range 0~70 °C")) - click.echo("{0:30s}{1:<40}".format(" ssd_all ", "execute all test iterms of SSD ")) - click.echo("{0:30s}{1:<40}".format(" ssd_help ", "list help menu \n")) - - -# -# check_pcie_speed -# check if the lnksta speed equals 5GT/s -# - -def check_pcie_speed(device = {}): - """ check_pcie_speed """ - - checkin = 0 - testname = "PCIE Speed Test" - - erroutput = open("/dev/null", "w") - for key, val in device.items(): - print("{0}".format(val.strip())) - command = "sudo lspci -vvvv -s " + key - proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=erroutput.fileno()) - proc.wait() - - output = proc.stdout.readlines() - - #shell cmd return failed - if proc.returncode > 0: - for line in output: - click.echo(line.strip()) - click.echo("shell cmd return faild: {0}".format(proc.returncode)) - return - else: - for line in output: - if "LnkSta:" in line: - click.echo(" {0}\n".format(line.strip())) - speed = line.split()[2] #LnkSta: Speed 5GT/s, Width x4, TrErr- Train- SlotClk+ DLActive- BWMgmt- ABWMgmt- - if speed == "5GT/s,": - checkin = 1 - else: - checkin = 0 - click.echo("LnkSta not match which is {0}".format(speed)) - erroutput.close() - echo_empty_line() - - -# -# 'pcie_speed' command #### -# - -@cli.command() -def pcie_lnkspeed(): - """Chedk the link speed""" - - device = {} - testname = "PCIE Speed Test" - command = "sudo lspci" - proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - output = proc.stdout.readlines() - (out, err) = proc.communicate() - - print_test_title(testname) - #shell cmd return failed - if proc.returncode > 0: - for line in output: - click.echo(line.strip()) - return - else: - for line in output: - if "b960" in line: - device[line.split()[0]] = line.strip() - - if len(device) != 2: - click.echo("get {0} device:".format(len(device))) - return - - check_pcie_speed(device) - - -# -# 'pcie_checkid' command #### -# - -@cli.command() -def pcie_checkid(): - """Chedk Vender ID and Devie ID Check""" - - checkin = 1 - testname = "Vender ID and Devie ID Check Test" - command = "sudo lspci -n | grep b960 " - proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - output = proc.stdout.readlines() - (out, err) = proc.communicate() - - print_test_title(testname) - #shell cmd return failed - if proc.returncode > 0: - for line in output: - click.echo(line.strip()) - return - else: - for line in output: - click.echo("Device: {0}".format(line.strip())) - venderid = line.split(":")[2].strip() - deviceid = line.split(":")[3].split()[0].strip() - click.echo(" Vender ID: {0}".format(venderid)) #01:00.0 0200: 14e4:b960 (rev 12) - click.echo(" Device ID: {0}\n".format(deviceid)) #01:00.0 0200: 14e4:b960 (rev 12) - if(venderid != "14e4") or (deviceid != "b960"): - checkin = 0 - - echo_empty_line() - - -# -# 'ssd-help' command #### -# - -@cli.command() -def pcie_help(): - """Show all SSD cmd""" - - click.echo("\nUsage: show [options]") - click.echo("========================= SHOW INFORMATION OPTIONS =========================\n") - click.echo("{0:30s}{1:<40}".format(" pcie_lnkspeed ", "Chedk the link speed")) - click.echo("{0:30s}{1:<40}".format(" pcie_checkid ", "Chedk Vender ID and Devie ID Check")) - click.echo("{0:30s}{1:<40}".format(" pcie_help ", "list help menu \n")) - -if __name__ == '__main__': - cli() +#! /usr/bin/python -u + +import errno +import json +import netaddr +import netifaces +import os +import re +import subprocess +import sys + +import click +from click_default_group import DefaultGroup +from natsort import natsorted +from tabulate import tabulate + +import sonic_platform +from swsssdk import ConfigDBConnector +from swsssdk import SonicV2Connector + +import mlnx + +SONIC_CFGGEN_PATH = '/usr/local/bin/sonic-cfggen' + +from pcieutil import PcieUtil + +try: + # noinspection PyPep8Naming + import ConfigParser as configparser +except ImportError: + # noinspection PyUnresolvedReferences + import configparser + + +# This is from the aliases example: +# https://github.com/pallets/click/blob/57c6f09611fc47ca80db0bd010f05998b3c0aa95/examples/aliases/aliases.py +class Config(object): + """Object to hold CLI config""" + + def __init__(self): + self.path = os.getcwd() + self.aliases = {} + + def read_config(self, filename): + parser = configparser.RawConfigParser() + parser.read([filename]) + try: + self.aliases.update(parser.items('aliases')) + except configparser.NoSectionError: + pass + +class InterfaceAliasConverter(object): + """Class which handles conversion between interface name and alias""" + + def __init__(self): + self.alias_max_length = 0 + + config_db = ConfigDBConnector() + config_db.connect() + self.port_dict = config_db.get_table('PORT') + + if not self.port_dict: + click.echo("port_dict is None!") + raise click.Abort() + + for port_name in self.port_dict.keys(): + try: + if self.alias_max_length < len( + self.port_dict[port_name]['alias']): + self.alias_max_length = len( + self.port_dict[port_name]['alias']) + except KeyError: + break + + def name_to_alias(self, interface_name): + """Return vendor interface alias if SONiC + interface name is given as argument + """ + if interface_name is not None: + for port_name in self.port_dict.keys(): + if interface_name == port_name: + return self.port_dict[port_name]['alias'] + + click.echo("Invalid interface {}".format(interface_name)) + raise click.Abort() + + def alias_to_name(self, interface_alias): + """Return SONiC interface name if vendor + port alias is given as argument + """ + if interface_alias is not None: + for port_name in self.port_dict.keys(): + if interface_alias == self.port_dict[port_name]['alias']: + return port_name + + click.echo("Invalid interface {}".format(interface_alias)) + raise click.Abort() + + +# Global Config object +_config = None + + +# This aliased group has been modified from click examples to inherit from DefaultGroup instead of click.Group. +# DefaultGroup is a superclass of click.Group which calls a default subcommand instead of showing +# a help message if no subcommand is passed +class AliasedGroup(DefaultGroup): + """This subclass of a DefaultGroup supports looking up aliases in a config + file and with a bit of magic. + """ + + 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: + _config = Config() + + # Load our config file + cfg_file = os.path.join(os.path.dirname(__file__), 'aliases.ini') + _config.read_config(cfg_file) + + # Try to get builtin commands as normal + rv = click.Group.get_command(self, ctx, cmd_name) + if rv is not None: + return rv + + # No builtin found. Look up an explicit command alias in the config + if cmd_name in _config.aliases: + actual_cmd = _config.aliases[cmd_name] + return click.Group.get_command(self, ctx, actual_cmd) + + # Alternative option: if we did not find an explicit alias we + # allow automatic abbreviation of the command. "status" for + # instance will match "st". We only allow that however if + # there is only one command. + matches = [x for x in self.list_commands(ctx) + if x.lower().startswith(cmd_name.lower())] + if not matches: + # No command name matched. Issue Default command. + ctx.arg0 = cmd_name + cmd_name = self.default_cmd_name + return DefaultGroup.get_command(self, ctx, cmd_name) + elif len(matches) == 1: + return DefaultGroup.get_command(self, ctx, matches[0]) + ctx.fail('Too many matches: %s' % ', '.join(sorted(matches))) + + +# To be enhanced. Routing-stack information should be collected from a global +# location (configdb?), so that we prevent the continous execution of this +# bash oneliner. To be revisited once routing-stack info is tracked somewhere. +def get_routing_stack(): + command = "sudo docker ps | grep bgp | awk '{print$2}' | cut -d'-' -f3 | cut -d':' -f1" + + try: + proc = subprocess.Popen(command, + stdout=subprocess.PIPE, + shell=True, + stderr=subprocess.STDOUT) + stdout = proc.communicate()[0] + proc.wait() + result = stdout.rstrip('\n') + + except OSError, e: + raise OSError("Cannot detect routing-stack") + + return (result) + + +# Global Routing-Stack variable +routing_stack = get_routing_stack() + + +def run_command(command, display_cmd=False): + 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 get_interface_mode() == "alias" and not command.startswith("intfutil"): + run_command_in_alias_mode(command) + raise sys.exit(0) + + proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + + while True: + output = proc.stdout.readline() + if output == "" and proc.poll() is not None: + break + if output: + click.echo(output.rstrip('\n')) + + rc = proc.poll() + if rc != 0: + sys.exit(rc) + + +def get_interface_mode(): + mode = os.getenv('SONIC_CLI_IFACE_MODE') + if mode is None: + mode = "default" + return mode + + +# Global class instance for SONiC interface name to alias conversion +iface_alias_converter = InterfaceAliasConverter() + + +def print_output_in_alias_mode(output, index): + """Convert and print all instances of SONiC interface + name to vendor-sepecific interface aliases. + """ + + alias_name = "" + interface_name = "" + + # Adjust tabulation width to length of alias name + if output.startswith("---"): + word = output.split() + dword = word[index] + underline = dword.rjust(iface_alias_converter.alias_max_length, + '-') + word[index] = underline + output = ' ' .join(word) + + # Replace SONiC interface name with vendor alias + word = output.split() + if word: + interface_name = word[index] + interface_name = interface_name.replace(':', '') + for port_name in natsorted(iface_alias_converter.port_dict.keys()): + if interface_name == port_name: + alias_name = iface_alias_converter.port_dict[port_name]['alias'] + if alias_name: + if len(alias_name) < iface_alias_converter.alias_max_length: + alias_name = alias_name.rjust( + iface_alias_converter.alias_max_length) + output = output.replace(interface_name, alias_name, 1) + + click.echo(output.rstrip('\n')) + + +def run_command_in_alias_mode(command): + """Run command and replace all instances of SONiC interface names + in output with vendor-sepecific interface aliases. + """ + + process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + + while True: + output = process.stdout.readline() + if output == '' and process.poll() is not None: + break + + if output: + index = 1 + raw_output = output + output = output.lstrip() + + if command.startswith("portstat"): + """Show interface counters""" + index = 0 + if output.startswith("IFACE"): + output = output.replace("IFACE", "IFACE".rjust( + iface_alias_converter.alias_max_length)) + print_output_in_alias_mode(output, index) + + elif command.startswith("intfstat"): + """Show RIF counters""" + index = 0 + if output.startswith("IFACE"): + output = output.replace("IFACE", "IFACE".rjust( + iface_alias_converter.alias_max_length)) + print_output_in_alias_mode(output, index) + + elif command == "pfcstat": + """Show pfc counters""" + index = 0 + if output.startswith("Port Tx"): + output = output.replace("Port Tx", "Port Tx".rjust( + iface_alias_converter.alias_max_length)) + + elif output.startswith("Port Rx"): + output = output.replace("Port Rx", "Port Rx".rjust( + iface_alias_converter.alias_max_length)) + print_output_in_alias_mode(output, index) + + elif (command.startswith("sudo sfputil show eeprom")): + """show interface transceiver eeprom""" + index = 0 + print_output_in_alias_mode(raw_output, index) + + elif (command.startswith("sudo sfputil show")): + """show interface transceiver lpmode, + presence + """ + index = 0 + if output.startswith("Port"): + output = output.replace("Port", "Port".rjust( + iface_alias_converter.alias_max_length)) + print_output_in_alias_mode(output, index) + + elif command == "sudo lldpshow": + """show lldp table""" + index = 0 + if output.startswith("LocalPort"): + output = output.replace("LocalPort", "LocalPort".rjust( + iface_alias_converter.alias_max_length)) + print_output_in_alias_mode(output, index) + + elif command.startswith("queuestat"): + """show queue counters""" + index = 0 + if output.startswith("Port"): + output = output.replace("Port", "Port".rjust( + iface_alias_converter.alias_max_length)) + print_output_in_alias_mode(output, index) + + elif command == "fdbshow": + """show mac""" + index = 3 + if output.startswith("No."): + output = " " + output + output = re.sub( + 'Type', ' Type', output) + elif output[0].isdigit(): + output = " " + output + print_output_in_alias_mode(output, index) + elif command.startswith("nbrshow"): + """show arp""" + index = 2 + if "Vlan" in output: + output = output.replace('Vlan', ' Vlan') + print_output_in_alias_mode(output, index) + + else: + if index: + for port_name in iface_alias_converter.port_dict.keys(): + regex = re.compile(r"\b{}\b".format(port_name)) + result = re.findall(regex, raw_output) + if result: + interface_name = ''.join(result) + if not raw_output.startswith(" PortID:"): + raw_output = raw_output.replace( + interface_name, + iface_alias_converter.name_to_alias( + interface_name)) + click.echo(raw_output.rstrip('\n')) + + rc = process.poll() + if rc != 0: + sys.exit(rc) + + +CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help', '-?']) + +# +# 'cli' group (root group) +# + +# This is our entrypoint - the main "show" command +# TODO: Consider changing function name to 'show' for better understandability +@click.group(cls=AliasedGroup, context_settings=CONTEXT_SETTINGS) +def cli(): + """SONiC command line - 'show' command""" + pass + + +# +# 'arp' command ("show arp") +# + +@cli.command() +@click.argument('ipaddress', required=False) +@click.option('-if', '--iface') +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def arp(ipaddress, iface, verbose): + """Show IP ARP table""" + cmd = "nbrshow -4" + + if ipaddress is not None: + cmd += " -ip {}".format(ipaddress) + + if iface is not None: + if get_interface_mode() == "alias": + if not ((iface.startswith("PortChannel")) or + (iface.startswith("eth"))): + iface = iface_alias_converter.alias_to_name(iface) + + cmd += " -if {}".format(iface) + + run_command(cmd, display_cmd=verbose) + +# +# 'ndp' command ("show ndp") +# + +@cli.command() +@click.argument('ip6address', required=False) +@click.option('-if', '--iface') +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def ndp(ip6address, iface, verbose): + """Show IPv6 Neighbour table""" + cmd = "nbrshow -6" + + if ip6address is not None: + cmd += " -ip {}".format(ip6address) + + if iface is not None: + cmd += " -if {}".format(iface) + + run_command(cmd, display_cmd=verbose) + +# +# 'interfaces' group ("show interfaces ...") +# + +@cli.group(cls=AliasedGroup, default_if_no_args=False) +def interfaces(): + """Show details of the network interfaces""" + pass + +# +# 'neighbor' group ### +# +@interfaces.group(cls=AliasedGroup, default_if_no_args=False) +def neighbor(): + """Show neighbor related information""" + pass + +# 'expected' subcommand ("show interface neighbor expected") +@neighbor.command() +@click.argument('interfacename', required=False) +def expected(interfacename): + """Show expected neighbor information by interfaces""" + neighbor_cmd = 'sonic-cfggen -d --var-json "DEVICE_NEIGHBOR"' + p1 = subprocess.Popen(neighbor_cmd, shell=True, stdout=subprocess.PIPE) + try : + neighbor_dict = json.loads(p1.stdout.read()) + except ValueError: + print("DEVICE_NEIGHBOR information is not present.") + return + + neighbor_metadata_cmd = 'sonic-cfggen -d --var-json "DEVICE_NEIGHBOR_METADATA"' + p2 = subprocess.Popen(neighbor_metadata_cmd, shell=True, stdout=subprocess.PIPE) + try : + neighbor_metadata_dict = json.loads(p2.stdout.read()) + except ValueError: + print("DEVICE_NEIGHBOR_METADATA information is not present.") + return + + #Swap Key and Value from interface: name to name: interface + device2interface_dict = {} + for port in natsorted(neighbor_dict.keys()): + device2interface_dict[neighbor_dict[port]['name']] = {'localPort': port, 'neighborPort': neighbor_dict[port]['port']} + + header = ['LocalPort', 'Neighbor', 'NeighborPort', 'NeighborLoopback', 'NeighborMgmt', 'NeighborType'] + body = [] + if interfacename: + for device in natsorted(neighbor_metadata_dict.keys()): + if device2interface_dict[device]['localPort'] == interfacename: + body.append([device2interface_dict[device]['localPort'], + device, + device2interface_dict[device]['neighborPort'], + neighbor_metadata_dict[device]['lo_addr'], + neighbor_metadata_dict[device]['mgmt_addr'], + neighbor_metadata_dict[device]['type']]) + else: + for device in natsorted(neighbor_metadata_dict.keys()): + body.append([device2interface_dict[device]['localPort'], + device, + device2interface_dict[device]['neighborPort'], + neighbor_metadata_dict[device]['lo_addr'], + neighbor_metadata_dict[device]['mgmt_addr'], + neighbor_metadata_dict[device]['type']]) + + click.echo(tabulate(body, header)) + + +@interfaces.group(cls=AliasedGroup, default_if_no_args=False) +def transceiver(): + """Show SFP Transceiver information""" + pass + + +@transceiver.command() +@click.argument('interfacename', required=False) +@click.option('-d', '--dom', 'dump_dom', is_flag=True, help="Also display Digital Optical Monitoring (DOM) data") +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def eeprom(interfacename, dump_dom, verbose): + """Show interface transceiver EEPROM information""" + + cmd = "sfpshow eeprom" + + if dump_dom: + cmd += " --dom" + + if interfacename is not None: + if get_interface_mode() == "alias": + interfacename = iface_alias_converter.alias_to_name(interfacename) + + cmd += " -p {}".format(interfacename) + + run_command(cmd, display_cmd=verbose) + + +@transceiver.command() +@click.argument('interfacename', required=False) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def lpmode(interfacename, verbose): + """Show interface transceiver low-power mode status""" + + cmd = "sudo sfputil show lpmode" + + if interfacename is not None: + if get_interface_mode() == "alias": + interfacename = iface_alias_converter.alias_to_name(interfacename) + + cmd += " -p {}".format(interfacename) + + run_command(cmd, display_cmd=verbose) + +@transceiver.command() +@click.argument('interfacename', required=False) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def presence(interfacename, verbose): + """Show interface transceiver presence""" + + cmd = "sfpshow presence" + + if interfacename is not None: + if get_interface_mode() == "alias": + interfacename = iface_alias_converter.alias_to_name(interfacename) + + cmd += " -p {}".format(interfacename) + + run_command(cmd, display_cmd=verbose) + + +@interfaces.command() +@click.argument('interfacename', required=False) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def description(interfacename, verbose): + """Show interface status, protocol and description""" + + cmd = "intfutil description" + + if interfacename is not None: + if get_interface_mode() == "alias": + interfacename = iface_alias_converter.alias_to_name(interfacename) + + cmd += " {}".format(interfacename) + + run_command(cmd, display_cmd=verbose) + + +@interfaces.command() +@click.argument('interfacename', required=False) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def status(interfacename, verbose): + """Show Interface status information""" + + cmd = "intfutil status" + + if interfacename is not None: + if get_interface_mode() == "alias": + interfacename = iface_alias_converter.alias_to_name(interfacename) + + cmd += " {}".format(interfacename) + + run_command(cmd, display_cmd=verbose) + + +# 'counters' subcommand ("show interfaces counters") +@interfaces.group(invoke_without_command=True) +@click.option('-a', '--printall', is_flag=True) +@click.option('-c', '--clear', is_flag=True) +@click.option('-p', '--period') +@click.option('--verbose', is_flag=True, help="Enable verbose output") +@click.pass_context +def counters(ctx, verbose, period, clear, printall): + """Show interface counters""" + + if ctx.invoked_subcommand is None: + cmd = "portstat" + + if clear: + cmd += " -c" + else: + if printall: + cmd += " -a" + if period is not None: + cmd += " -p {}".format(period) + + run_command(cmd, display_cmd=verbose) + +# 'counters' subcommand ("show interfaces counters rif") +@counters.command() +@click.argument('interface', metavar='', required=False, type=str) +@click.option('-p', '--period') +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def rif(interface, period, verbose): + """Show interface counters""" + + cmd = "intfstat" + if period is not None: + cmd += " -p {}".format(period) + if interface is not None: + cmd += " -i {}".format(interface) + + run_command(cmd, display_cmd=verbose) + +# 'portchannel' subcommand ("show interfaces portchannel") +@interfaces.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def portchannel(verbose): + """Show PortChannel information""" + cmd = "teamshow" + run_command(cmd, display_cmd=verbose) + +# +# 'pfc' group ("show pfc ...") +# + +@cli.group(cls=AliasedGroup, default_if_no_args=False) +def pfc(): + """Show details of the priority-flow-control (pfc) """ + pass + +# 'counters' subcommand ("show interfaces pfccounters") +@pfc.command() +@click.option('-c', '--clear', is_flag=True) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def counters(clear, verbose): + """Show pfc counters""" + + cmd = "pfcstat" + + if clear: + cmd += " -c" + + run_command(cmd, display_cmd=verbose) + +# 'naming_mode' subcommand ("show interfaces naming_mode") +@interfaces.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def naming_mode(verbose): + """Show interface naming_mode status""" + + click.echo(get_interface_mode()) + + +# +# 'watermark' group ("show watermark telemetry interval") +# + +@cli.group(cls=AliasedGroup, default_if_no_args=False) +def watermark(): + """Show details of watermark """ + pass + +@watermark.group() +def telemetry(): + """Show watermark telemetry info""" + pass + +@telemetry.command('interval') +def show_tm_interval(): + """Show telemetry interval""" + command = 'watermarkcfg --show-interval' + run_command(command) + + +# +# 'queue' group ("show queue ...") +# + +@cli.group(cls=AliasedGroup, default_if_no_args=False) +def queue(): + """Show details of the queues """ + pass + +# 'queuecounters' subcommand ("show queue counters") +@queue.command() +@click.argument('interfacename', required=False) +@click.option('-c', '--clear', is_flag=True) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def counters(interfacename, clear, verbose): + """Show queue counters""" + + cmd = "queuestat" + + if interfacename is not None: + if get_interface_mode() == "alias": + interfacename = iface_alias_converter.alias_to_name(interfacename) + + if clear: + cmd += " -c" + else: + if interfacename is not None: + cmd += " -p {}".format(interfacename) + + run_command(cmd, display_cmd=verbose) + +# watermarks subcommands ("show queue watermarks|persistent-watermarks") + +@queue.group() +def watermark(): + """Show queue user WM""" + pass + +@watermark.command('unicast') +def wm_q_uni(): + """Show user WM for unicast queues""" + command = 'watermarkstat -t q_shared_uni' + run_command(command) + +@watermark.command('multicast') +def wm_q_multi(): + """Show user WM for multicast queues""" + command = 'watermarkstat -t q_shared_multi' + run_command(command) + +@queue.group(name='persistent-watermark') +def persistent_watermark(): + """Show queue persistent WM""" + pass + +@persistent_watermark.command('unicast') +def pwm_q_uni(): + """Show persistent WM for persistent queues""" + command = 'watermarkstat -p -t q_shared_uni' + run_command(command) + +@persistent_watermark.command('multicast') +def pwm_q_multi(): + """Show persistent WM for multicast queues""" + command = 'watermarkstat -p -t q_shared_multi' + run_command(command) + + +# +# 'priority-group' group ("show priority-group ...") +# + +@cli.group(name='priority-group', cls=AliasedGroup, default_if_no_args=False) +def priority_group(): + """Show details of the PGs """ + + +@priority_group.group() +def watermark(): + """Show priority_group user WM""" + pass + +@watermark.command('headroom') +def wm_pg_headroom(): + """Show user headroom WM for pg""" + 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' + run_command(command) + +@priority_group.group(name='persistent-watermark') +def persistent_watermark(): + """Show queue persistent WM""" + pass + +@persistent_watermark.command('headroom') +def pwm_pg_headroom(): + """Show persistent headroom WM for pg""" + 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' + run_command(command) + + +# +# 'mac' command ("show mac ...") +# + +@cli.command() +@click.option('-v', '--vlan') +@click.option('-p', '--port') +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def mac(vlan, port, verbose): + """Show MAC (FDB) entries""" + + cmd = "fdbshow" + + if vlan is not None: + cmd += " -v {}".format(vlan) + + if port is not None: + cmd += " -p {}".format(port) + + run_command(cmd, display_cmd=verbose) + +# +# 'show route-map' command ("show route-map") +# + +@cli.command('route-map') +@click.argument('route_map_name', required=False) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def route_map(route_map_name, verbose): + """show route-map""" + cmd = 'sudo vtysh -c "show route-map' + if route_map_name is not None: + cmd += ' {}'.format(route_map_name) + cmd += '"' + run_command(cmd, display_cmd=verbose) + +# +# 'ip' group ("show ip ...") +# + +# This group houses IP (i.e., IPv4) commands and subgroups +@cli.group(cls=AliasedGroup, default_if_no_args=False) +def ip(): + """Show IP (IPv4) commands""" + pass + + +# +# get_if_admin_state +# +# Given an interface name, return its admin state reported by the kernel. +# +def get_if_admin_state(iface): + admin_file = "/sys/class/net/{0}/flags" + + try: + state_file = open(admin_file.format(iface), "r") + except IOError as e: + print "Error: unable to open file: %s" % str(e) + return "error" + + content = state_file.readline().rstrip() + flags = int(content, 16) + + if flags & 0x1: + return "up" + else: + return "down" + + +# +# get_if_oper_state +# +# Given an interface name, return its oper state reported by the kernel. +# +def get_if_oper_state(iface): + oper_file = "/sys/class/net/{0}/carrier" + + try: + state_file = open(oper_file.format(iface), "r") + except IOError as e: + print "Error: unable to open file: %s" % str(e) + return "error" + + oper_state = state_file.readline().rstrip() + if oper_state == "1": + return "up" + else: + return "down" + + +# +# 'show ip interfaces' command +# +# Display all interfaces with an IPv4 address and their admin/oper states. +# Addresses from all scopes are included. Interfaces with no addresses are +# excluded. +# +@ip.command() +def interfaces(): + """Show interfaces IPv4 address""" + header = ['Interface', 'IPv4 address/mask', 'Admin/Oper'] + data = [] + + interfaces = natsorted(netifaces.interfaces()) + + for iface in interfaces: + ipaddresses = netifaces.ifaddresses(iface) + + if netifaces.AF_INET in ipaddresses: + ifaddresses = [] + for ipaddr in ipaddresses[netifaces.AF_INET]: + netmask = netaddr.IPAddress(ipaddr['netmask']).netmask_bits() + ifaddresses.append(["", str(ipaddr['addr']) + "/" + str(netmask)]) + + if len(ifaddresses) > 0: + admin = get_if_admin_state(iface) + if admin == "up": + oper = get_if_oper_state(iface) + else: + oper = "down" + data.append([iface, ifaddresses[0][1], admin + "/" + oper]) + for ifaddr in ifaddresses[1:]: + data.append(["", ifaddr[1], ""]) + + print tabulate(data, header, tablefmt="simple", stralign='left', missingval="") + + +# +# 'route' subcommand ("show ip route") +# + +@ip.command() +@click.argument('ipaddress', required=False) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def route(ipaddress, verbose): + """Show IP (IPv4) routing table""" + cmd = 'sudo vtysh -c "show ip route' + + if ipaddress is not None: + cmd += ' {}'.format(ipaddress) + + cmd += '"' + + run_command(cmd, display_cmd=verbose) + +# +# 'prefix-list' subcommand ("show ip prefix-list") +# + +@ip.command('prefix-list') +@click.argument('prefix_list_name', required=False) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def prefix_list(prefix_list_name, verbose): + """show ip prefix-list""" + cmd = 'sudo vtysh -c "show ip prefix-list' + if prefix_list_name is not None: + cmd += ' {}'.format(prefix_list_name) + cmd += '"' + run_command(cmd, display_cmd=verbose) + + +# 'protocol' command +@ip.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def protocol(verbose): + """Show IPv4 protocol information""" + cmd = 'sudo vtysh -c "show ip protocol"' + run_command(cmd, display_cmd=verbose) + + +# +# 'ipv6' group ("show ipv6 ...") +# + +# This group houses IPv6-related commands and subgroups +@cli.group(cls=AliasedGroup, default_if_no_args=False) +def ipv6(): + """Show IPv6 commands""" + pass + + +# +# 'show ipv6 interfaces' command +# +# Display all interfaces with an IPv6 address and their admin/oper states. +# Addresses from all scopes are included. Interfaces with no addresses are +# excluded. +# +@ipv6.command() +def interfaces(): + """Show interfaces IPv6 address""" + header = ['Interface', 'IPv6 address/mask', 'Admin/Oper'] + data = [] + + interfaces = natsorted(netifaces.interfaces()) + + for iface in interfaces: + ipaddresses = netifaces.ifaddresses(iface) + + if netifaces.AF_INET6 in ipaddresses: + ifaddresses = [] + for ipaddr in ipaddresses[netifaces.AF_INET6]: + netmask = ipaddr['netmask'].split('/', 1)[-1] + ifaddresses.append(["", str(ipaddr['addr']) + "/" + str(netmask)]) + + if len(ifaddresses) > 0: + admin = get_if_admin_state(iface) + if admin == "up": + oper = get_if_oper_state(iface) + else: + oper = "down" + data.append([iface, ifaddresses[0][1], admin + "/" + oper]) + for ifaddr in ifaddresses[1:]: + data.append(["", ifaddr[1], ""]) + + print tabulate(data, header, tablefmt="simple", stralign='left', missingval="") + + +# +# 'route' subcommand ("show ipv6 route") +# + +@ipv6.command() +@click.argument('ipaddress', required=False) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def route(ipaddress, verbose): + """Show IPv6 routing table""" + cmd = 'sudo vtysh -c "show ipv6 route' + + if ipaddress is not None: + cmd += ' {}'.format(ipaddress) + + cmd += '"' + + run_command(cmd, display_cmd=verbose) + + +# 'protocol' command +@ipv6.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def protocol(verbose): + """Show IPv6 protocol information""" + cmd = 'sudo vtysh -c "show ipv6 protocol"' + run_command(cmd, display_cmd=verbose) + + +# +# Inserting BGP functionality into cli's show parse-chain. +# BGP commands are determined by the routing-stack being elected. +# +if routing_stack == "quagga": + from .bgp_quagga_v4 import bgp + ip.add_command(bgp) + from .bgp_quagga_v6 import bgp + ipv6.add_command(bgp) +elif routing_stack == "frr": + @cli.command() + @click.argument('bgp_args', nargs = -1, required = False) + @click.option('--verbose', is_flag=True, help="Enable verbose output") + def bgp(bgp_args, verbose): + """BGP information""" + bgp_cmd = "show bgp" + for arg in bgp_args: + bgp_cmd += " " + str(arg) + cmd = 'sudo vtysh -c "{}"'.format(bgp_cmd) + run_command(cmd, display_cmd=verbose) + + +# +# 'lldp' group ("show lldp ...") +# + +@cli.group(cls=AliasedGroup, default_if_no_args=False) +def lldp(): + """LLDP (Link Layer Discovery Protocol) information""" + pass + +# Default 'lldp' command (called if no subcommands or their aliases were passed) +@lldp.command() +@click.argument('interfacename', required=False) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def neighbors(interfacename, verbose): + """Show LLDP neighbors""" + cmd = "sudo lldpctl" + + if interfacename is not None: + if get_interface_mode() == "alias": + interfacename = iface_alias_converter.alias_to_name(interfacename) + + cmd += " {}".format(interfacename) + + run_command(cmd, display_cmd=verbose) + +# 'table' subcommand ("show lldp table") +@lldp.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def table(verbose): + """Show LLDP neighbors in tabular format""" + cmd = "sudo lldpshow" + run_command(cmd, display_cmd=verbose) + +# +# 'platform' group ("show platform ...") +# + +@cli.group(cls=AliasedGroup, default_if_no_args=False) +def platform(): + """Show platform-specific hardware info""" + pass + +version_info = sonic_platform.get_sonic_version_info() +if (version_info and version_info.get('asic_type') == 'mellanox'): + platform.add_command(mlnx.mlnx) + +# 'summary' subcommand ("show platform summary") +@platform.command() +def summary(): + """Show hardware platform information""" + machine_info = sonic_platform.get_machine_info() + platform = sonic_platform.get_platform_info(machine_info) + + config_db = ConfigDBConnector() + config_db.connect() + data = config_db.get_table('DEVICE_METADATA') + + try: + hwsku = data['localhost']['hwsku'] + except KeyError: + hwsku = "Unknown" + + version_info = sonic_platform.get_sonic_version_info() + asic_type = version_info['asic_type'] + + click.echo("Platform: {}".format(platform)) + click.echo("HwSKU: {}".format(hwsku)) + click.echo("ASIC: {}".format(asic_type)) + +# 'syseeprom' subcommand ("show platform syseeprom") +@platform.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def syseeprom(verbose): + """Show system EEPROM information""" + cmd = "sudo decode-syseeprom -d" + run_command(cmd, display_cmd=verbose) + +# 'psustatus' subcommand ("show platform psustatus") +@platform.command() +@click.option('-i', '--index', default=-1, type=int, help="the index of PSU") +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def psustatus(index, verbose): + """Show PSU status information""" + cmd = "psushow -s" + + if index >= 0: + cmd += " -i {}".format(index) + + run_command(cmd, display_cmd=verbose) + +# +# 'logging' command ("show logging") +# + + +@platform.group(cls=AliasedGroup, default_if_no_args=False) +def ssd(): + """Show ssd related information""" + pass + + +@ssd.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +@click.argument("device") +def firmware(verbose, device): + """Show SSD Firmware Info""" + cmd = "ssdutil firmware " + device + run_command(cmd, display_cmd=verbose) + +@ssd.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +@click.argument("device") +def serialNum(verbose, device): + """Show SSD Serial Number""" + cmd = "ssdutil serialnum " + device + run_command(cmd, display_cmd=verbose) + +@ssd.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +@click.argument("device") +def peCycle(verbose, device): + """Show SSD P/E Cycle""" + cmd = "ssdutil pecycle " + device + run_command(cmd, display_cmd=verbose) + +@ssd.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +@click.argument("device") +def capacity(verbose, device): + """Show SSD Capacity""" + cmd = "ssdutil capacity " + device + run_command(cmd, display_cmd=verbose) + +@ssd.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +@click.argument("device") +def temp(verbose, device): + """Show SSD Temperature""" + cmd = "ssdutil temp " + device + run_command(cmd, display_cmd=verbose) + +@ssd.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +@click.argument("device") +def badBlock(verbose, device): + """Show SSD Badblock""" + cmd = "ssdutil badblock " + device + run_command(cmd, display_cmd=verbose) + +@ssd.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +@click.argument("device") +def health(verbose, device): + """Show SSD Health""" + cmd = "ssdutil health " + device + run_command(cmd, display_cmd=verbose) + +@ssd.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +@click.argument("device") +def remainTime(verbose, device): + """Show SSD Remaining Time""" + cmd = "ssdutil remaintime " + device + run_command(cmd, display_cmd=verbose) + + +@ssd.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +@click.argument("device") +def device(verbose, device): + """Show SSD Device Model""" + cmd = "ssdutil device " + device + run_command(cmd, display_cmd=verbose) + + +@cli.command() +@click.argument('process', required=False) +@click.option('-l', '--lines') +@click.option('-f', '--follow', is_flag=True) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def logging(process, lines, follow, verbose): + """Show system log""" + if follow: + cmd = "sudo tail -F /var/log/syslog" + run_command(cmd, display_cmd=verbose) + else: + if os.path.isfile("/var/log/syslog.1"): + cmd = "sudo cat /var/log/syslog.1 /var/log/syslog" + else: + cmd = "sudo cat /var/log/syslog" + + if process is not None: + cmd += " | grep '{}'".format(process) + + if lines is not None: + cmd += " | tail -{}".format(lines) + + run_command(cmd, display_cmd=verbose) + + +# +# 'version' command ("show version") +# + +@cli.command() +def version(): + """Show version information""" + version_info = sonic_platform.get_sonic_version_info() + + click.echo("SONiC Software Version: SONiC.{}".format(version_info['build_version'])) + click.echo("Distribution: Debian {}".format(version_info['debian_version'])) + click.echo("Kernel: {}".format(version_info['kernel_version'])) + click.echo("Build commit: {}".format(version_info['commit_id'])) + click.echo("Build date: {}".format(version_info['build_date'])) + click.echo("Built by: {}".format(version_info['built_by'])) + + click.echo("\nDocker images:") + cmd = 'sudo docker images --format "table {{.Repository}}\\t{{.Tag}}\\t{{.ID}}\\t{{.Size}}"' + p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) + click.echo(p.stdout.read()) + +# +# 'environment' command ("show environment") +# + +@cli.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def environment(verbose): + """Show environmentals (voltages, fans, temps)""" + cmd = "sudo sensors" + run_command(cmd, display_cmd=verbose) + + +# +# 'processes' group ("show processes ...") +# + +@cli.group(cls=AliasedGroup, default_if_no_args=False) +def processes(): + """Display process information""" + pass + +@processes.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def summary(verbose): + """Show processes info""" + # Run top batch mode to prevent unexpected newline after each newline + cmd = "ps -eo pid,ppid,cmd,%mem,%cpu " + run_command(cmd, display_cmd=verbose) + + +# 'cpu' subcommand ("show processes cpu") +@processes.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def cpu(verbose): + """Show processes CPU info""" + # Run top in batch mode to prevent unexpected newline after each newline + cmd = "top -bn 1 -o %CPU" + run_command(cmd, display_cmd=verbose) + +# 'memory' subcommand +@processes.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def memory(verbose): + """Show processes memory info""" + # Run top batch mode to prevent unexpected newline after each newline + cmd = "top -bn 1 -o %MEM" + run_command(cmd, display_cmd=verbose) + +# +# 'users' command ("show users") +# + +@cli.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def users(verbose): + """Show users""" + cmd = "who" + run_command(cmd, display_cmd=verbose) + + +# +# 'techsupport' command ("show techsupport") +# + +@cli.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def techsupport(verbose): + """Gather information for troubleshooting""" + cmd = "sudo generate_dump -v" + run_command(cmd, display_cmd=verbose) + + +# +# 'runningconfiguration' group ("show runningconfiguration") +# + +@cli.group(cls=AliasedGroup, default_if_no_args=False) +def runningconfiguration(): + """Show current running configuration information""" + pass + + +# 'all' subcommand ("show runningconfiguration all") +@runningconfiguration.command() +@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) + + +# 'bgp' subcommand ("show runningconfiguration bgp") +@runningconfiguration.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def bgp(verbose): + """Show BGP running configuration""" + cmd = 'sudo vtysh -c "show running-config"' + run_command(cmd, display_cmd=verbose) + + +# 'interfaces' subcommand ("show runningconfiguration interfaces") +@runningconfiguration.command() +@click.argument('interfacename', required=False) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def interfaces(interfacename, verbose): + """Show interfaces running configuration""" + cmd = "cat /etc/network/interfaces" + + if interfacename is not None: + cmd += " | grep {} -A 4".format(interfacename) + + run_command(cmd, display_cmd=verbose) + + +# 'snmp' subcommand ("show runningconfiguration snmp") +@runningconfiguration.command() +@click.argument('server', required=False) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def snmp(server, verbose): + """Show SNMP information""" + cmd = "sudo docker exec snmp cat /etc/snmp/snmpd.conf" + + if server is not None: + cmd += " | grep -i agentAddress" + + run_command(cmd, display_cmd=verbose) + + +# 'ntp' subcommand ("show runningconfiguration ntp") +@runningconfiguration.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def ntp(verbose): + """Show NTP running configuration""" + cmd = "cat /etc/ntp.conf" + run_command(cmd, display_cmd=verbose) + + +# +# 'startupconfiguration' group ("show startupconfiguration ...") +# + +@cli.group(cls=AliasedGroup, default_if_no_args=False) +def startupconfiguration(): + """Show startup configuration information""" + pass + + +# 'bgp' subcommand ("show startupconfiguration bgp") +@startupconfiguration.command() +@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) + result = proc.stdout.read().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) + elif result == "frr": + 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) + else: + click.echo("Unidentified routing-stack") + +# +# 'ntp' command ("show ntp") +# + +@cli.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def ntp(verbose): + """Show NTP information""" + cmd = "ntpq -p -n" + run_command(cmd, display_cmd=verbose) + + +# +# 'uptime' command ("show uptime") +# + +@cli.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def uptime(verbose): + """Show system uptime""" + 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" + 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" + run_command(cmd, display_cmd=verbose) + +@cli.group(cls=AliasedGroup, default_if_no_args=False) +def vlan(): + """Show VLAN information""" + pass + +@vlan.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def brief(verbose): + """Show all bridge information""" + config_db = ConfigDBConnector() + config_db.connect() + header = ['VLAN ID', 'IP Address', 'Ports', 'Port Tagging', 'DHCP Helper Address'] + body = [] + vlan_keys = [] + + # Fetching data from config_db for VLAN, VLAN_INTERFACE and VLAN_MEMBER + vlan_dhcp_helper_data = config_db.get_table('VLAN') + vlan_ip_data = config_db.get_table('VLAN_INTERFACE') + vlan_ports_data = config_db.get_table('VLAN_MEMBER') + + vlan_keys = natsorted(vlan_dhcp_helper_data.keys()) + + # Defining dictionaries for DHCP Helper address, Interface Gateway IP, + # VLAN ports and port tagging + vlan_dhcp_helper_dict = {} + vlan_ip_dict = {} + vlan_ports_dict = {} + vlan_tagging_dict = {} + + # Parsing DHCP Helpers info + for key in natsorted(vlan_dhcp_helper_data.keys()): + try: + if vlan_dhcp_helper_data[key]['dhcp_servers']: + vlan_dhcp_helper_dict[str(key.strip('Vlan'))] = vlan_dhcp_helper_data[key]['dhcp_servers'] + except KeyError: + vlan_dhcp_helper_dict[str(key.strip('Vlan'))] = " " + pass + + # Parsing VLAN Gateway info + for key in natsorted(vlan_ip_data.keys()): + interface_key = str(key[0].strip("Vlan")) + interface_value = str(key[1]) + if interface_key in vlan_ip_dict: + vlan_ip_dict[interface_key].append(interface_value) + else: + vlan_ip_dict[interface_key] = [interface_value] + + # Parsing VLAN Ports info + for key in natsorted(vlan_ports_data.keys()): + ports_key = str(key[0].strip("Vlan")) + ports_value = str(key[1]) + ports_tagging = vlan_ports_data[key]['tagging_mode'] + if ports_key in vlan_ports_dict: + vlan_ports_dict[ports_key].append(ports_value) + else: + vlan_ports_dict[ports_key] = [ports_value] + if ports_key in vlan_tagging_dict: + vlan_tagging_dict[ports_key].append(ports_tagging) + else: + vlan_tagging_dict[ports_key] = [ports_tagging] + + # Printing the following dictionaries in tablular forms: + # vlan_dhcp_helper_dict={}, vlan_ip_dict = {}, vlan_ports_dict = {} + # vlan_tagging_dict = {} + for key in natsorted(vlan_dhcp_helper_dict.keys()): + if key not in vlan_ip_dict: + ip_address = "" + else: + ip_address = ','.replace(',', '\n').join(vlan_ip_dict[key]) + if key not in vlan_ports_dict: + vlan_ports = "" + else: + vlan_ports = ','.replace(',', '\n').join((vlan_ports_dict[key])) + if key not in vlan_dhcp_helper_dict: + dhcp_helpers = "" + else: + dhcp_helpers = ','.replace(',', '\n').join(vlan_dhcp_helper_dict[key]) + if key not in vlan_tagging_dict: + vlan_tagging = "" + else: + vlan_tagging = ','.replace(',', '\n').join((vlan_tagging_dict[key])) + body.append([key, ip_address, vlan_ports, vlan_tagging, dhcp_helpers]) + click.echo(tabulate(body, header, tablefmt="grid")) + +@vlan.command() +@click.option('-s', '--redis-unix-socket-path', help='unix socket path for redis connection') +def config(redis_unix_socket_path): + kwargs = {} + if redis_unix_socket_path: + kwargs['unix_socket_path'] = redis_unix_socket_path + config_db = ConfigDBConnector(**kwargs) + config_db.connect(wait_for_init=False) + data = config_db.get_table('VLAN') + keys = data.keys() + + def tablelize(keys, data): + table = [] + + for k in keys: + for m in data[k].get('members', []): + r = [] + r.append(k) + r.append(data[k]['vlanid']) + if get_interface_mode() == "alias": + alias = iface_alias_converter.name_to_alias(m) + r.append(alias) + else: + r.append(m) + + entry = config_db.get_entry('VLAN_MEMBER', (k, m)) + mode = entry.get('tagging_mode') + if mode == None: + r.append('?') + else: + r.append(mode) + + table.append(r) + + return table + + header = ['Name', 'VID', 'Member', 'Mode'] + click.echo(tabulate(tablelize(keys, data), header)) + +@cli.command('services') +def services(): + """Show all daemon services""" + cmd = "sudo docker ps --format '{{.Names}}'" + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=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) + print proc1.stdout.read() + else: + break + +@cli.command() +def aaa(): + """Show AAA configuration""" + config_db = ConfigDBConnector() + config_db.connect() + data = config_db.get_table('AAA') + output = '' + + aaa = { + 'authentication': { + 'login': 'local (default)', + 'failthrough': 'True (default)', + 'fallback': 'True (default)' + } + } + if 'authentication' in data: + aaa['authentication'].update(data['authentication']) + for row in aaa: + entry = aaa[row] + for key in entry: + output += ('AAA %s %s %s\n' % (row, key, str(entry[key]))) + click.echo(output) + + +@cli.command() +def tacacs(): + """Show TACACS+ configuration""" + config_db = ConfigDBConnector() + config_db.connect() + output = '' + data = config_db.get_table('TACPLUS') + + tacplus = { + 'global': { + 'auth_type': 'pap (default)', + 'timeout': '5 (default)', + 'passkey': ' (default)' + } + } + if 'global' in data: + tacplus['global'].update(data['global']) + for key in tacplus['global']: + output += ('TACPLUS global %s %s\n' % (str(key), str(tacplus['global'][key]))) + + data = config_db.get_table('TACPLUS_SERVER') + if data != {}: + for row in data: + entry = data[row] + output += ('\nTACPLUS_SERVER address %s\n' % row) + for key in entry: + output += (' %s %s\n' % (key, str(entry[key]))) + click.echo(output) + +# +# 'mirror_session' command ("show mirror_session ...") +# +@cli.command() +@click.argument('session_name', required=False) +@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" + + if session_name is not None: + cmd += " {}".format(session_name) + + run_command(cmd, display_cmd=verbose) + + +# +# 'acl' group ### +# + +@cli.group(cls=AliasedGroup, default_if_no_args=False) +def acl(): + """Show ACL related information""" + pass + + +# 'rule' subcommand ("show acl rule") +@acl.command() +@click.argument('table_name', required=False) +@click.argument('rule_id', required=False) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def rule(table_name, rule_id, verbose): + """Show existing ACL rules""" + cmd = "acl-loader show rule" + + if table_name is not None: + cmd += " {}".format(table_name) + + if rule_id is not None: + cmd += " {}".format(rule_id) + + run_command(cmd, display_cmd=verbose) + + +# 'table' subcommand ("show acl table") +@acl.command() +@click.argument('table_name', required=False) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def table(table_name, verbose): + """Show existing ACL tables""" + cmd = "acl-loader show table" + + if table_name is not None: + cmd += " {}".format(table_name) + + run_command(cmd, display_cmd=verbose) + + +# +# 'ecn' command ("show ecn") +# +@cli.command('ecn') +def ecn(): + """Show ECN configuration""" + cmd = "ecnconfig -l" + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) + click.echo(proc.stdout.read()) + + +# 'mmu' command ("show mmu") +# +@cli.command('mmu') +def mmu(): + """Show mmu configuration""" + cmd = "mmuconfig -l" + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) + click.echo(proc.stdout.read()) + + +# +# 'reboot-cause' command ("show reboot-cause") +# +@cli.command('reboot-cause') +def reboot_cause(): + """Show cause of most recent reboot""" + PREVIOUS_REBOOT_CAUSE_FILE = "/host/reboot-cause/previous-reboot-cause.txt" + + # At boot time, PREVIOUS_REBOOT_CAUSE_FILE is generated based on + # the contents of the 'reboot cause' file as it was left when the device + # went down for reboot. This file should always be created at boot, + # but check first just in case it's not present. + if not os.path.isfile(PREVIOUS_REBOOT_CAUSE_FILE): + click.echo("Unable to determine cause of previous reboot\n") + else: + cmd = "cat {}".format(PREVIOUS_REBOOT_CAUSE_FILE) + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) + click.echo(proc.stdout.read()) + + +# +# 'line' command ("show line") +# +@cli.command('line') +def line(): + """Show all /dev/ttyUSB lines and their info""" + cmd = "consutil show" + run_command(cmd, display_cmd=verbose) + return + + +@cli.group(cls=AliasedGroup, default_if_no_args=False) +def warm_restart(): + """Show warm restart configuration and state""" + pass + +@warm_restart.command() +@click.option('-s', '--redis-unix-socket-path', help='unix socket path for redis connection') +def state(redis_unix_socket_path): + """Show warm restart state""" + kwargs = {} + if redis_unix_socket_path: + kwargs['unix_socket_path'] = redis_unix_socket_path + + data = {} + db = SonicV2Connector(host='127.0.0.1') + db.connect(db.STATE_DB, False) # Make one attempt only + + TABLE_NAME_SEPARATOR = '|' + prefix = 'WARM_RESTART_TABLE' + TABLE_NAME_SEPARATOR + _hash = '{}{}'.format(prefix, '*') + table_keys = db.keys(db.STATE_DB, _hash) + + def remove_prefix(text, prefix): + if text.startswith(prefix): + return text[len(prefix):] + return text + + table = [] + for tk in table_keys: + entry = db.get_all(db.STATE_DB, tk) + r = [] + r.append(remove_prefix(tk, prefix)) + if 'restore_count' not in entry: + r.append("") + else: + r.append(entry['restore_count']) + + if 'state' not in entry: + r.append("") + else: + r.append(entry['state']) + + table.append(r) + + header = ['name', 'restore_count', 'state'] + click.echo(tabulate(table, header)) + +@warm_restart.command() +@click.option('-s', '--redis-unix-socket-path', help='unix socket path for redis connection') +def config(redis_unix_socket_path): + """Show warm restart config""" + kwargs = {} + if redis_unix_socket_path: + kwargs['unix_socket_path'] = redis_unix_socket_path + config_db = ConfigDBConnector(**kwargs) + config_db.connect(wait_for_init=False) + data = config_db.get_table('WARM_RESTART') + # Python dictionary keys() Method + keys = data.keys() + + state_db = SonicV2Connector(host='127.0.0.1') + state_db.connect(state_db.STATE_DB, False) # Make one attempt only + TABLE_NAME_SEPARATOR = '|' + prefix = 'WARM_RESTART_ENABLE_TABLE' + TABLE_NAME_SEPARATOR + _hash = '{}{}'.format(prefix, '*') + # DBInterface keys() method + enable_table_keys = state_db.keys(state_db.STATE_DB, _hash) + + def tablelize(keys, data, enable_table_keys, prefix): + table = [] + + if enable_table_keys is not None: + for k in enable_table_keys: + k = k.replace(prefix, "") + if k not in keys: + keys.append(k) + + for k in keys: + r = [] + r.append(k) + + enable_k = prefix + k + if enable_table_keys is None or enable_k not in enable_table_keys: + r.append("false") + else: + r.append(state_db.get(state_db.STATE_DB, enable_k, "enable")) + + if k not in data: + r.append("NULL") + r.append("NULL") + elif 'neighsyncd_timer' in data[k]: + r.append("neighsyncd_timer") + r.append(data[k]['neighsyncd_timer']) + elif 'bgp_timer' in data[k]: + r.append("bgp_timer") + r.append(data[k]['bgp_timer']) + elif 'teamsyncd_timer' in data[k]: + r.append("teamsyncd_timer") + r.append(data[k]['teamsyncd_timer']) + else: + r.append("NULL") + r.append("NULL") + + table.append(r) + + return table + + header = ['name', 'enable', 'timer_name', 'timer_duration'] + click.echo(tabulate(tablelize(keys, data, enable_table_keys, prefix), header)) + state_db.close(state_db.STATE_DB) + + +if __name__ == '__main__': + cli() diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..12c2570 --- /dev/null +++ b/setup.py @@ -0,0 +1,149 @@ +# https://github.com/ninjaaron/fast-entry_points +# workaround for slow 'pkg_resources' import +# +# NOTE: this only has effect on console_scripts and no speed-up for commands +# under scripts/. Consider stop using scripts and use console_scripts instead +# +# https://stackoverflow.com/questions/18787036/difference-between-entry-points-console-scripts-and-scripts-in-setup-py +try: + import fastentrypoints +except ImportError: + from setuptools.command import easy_install + import pkg_resources + easy_install.main(['fastentrypoints']) + pkg_resources.require('fastentrypoints') + import fastentrypoints + +import glob +from setuptools import setup + +setup( + name='sonic-utilities', + version='1.2', + description='Command-line utilities for SONiC', + license='Apache 2.0', + author='SONiC Team', + author_email='linuxnetdev@microsoft.com', + url='https://github.com/Azure/sonic-utilities', + maintainer='Joe LeVeque', + maintainer_email='jolevequ@microsoft.com', + packages=[ + 'acl_loader', + 'clear', + 'config', + 'connect', + 'consutil', + 'counterpoll', + 'crm', + 'debug', + 'pfcwd', + 'sfputil', + 'pfc', + 'psuutil', + 'show', + 'ssdutil', + 'ssdutil/ssdvendors', + 'sonic_installer', + 'sonic-utilities-tests', + 'undebug', + 'utilities_common', + ], + package_data={ + 'show': ['aliases.ini'], + 'sonic-utilities-tests': ['acl_input/*', 'mock_tables/*.py', 'mock_tables/*.json'] + }, + scripts=[ + 'scripts/aclshow', + 'scripts/boot_part', + 'scripts/coredump-compress', + 'scripts/db_migrator.py', + 'scripts/decode-syseeprom', + 'scripts/dropcheck', + 'scripts/ecnconfig', + 'scripts/fast-reboot', + 'scripts/fast-reboot-dump.py', + 'scripts/fdbclear', + 'scripts/fdbshow', + 'scripts/generate_dump', + 'scripts/intfutil', + 'scripts/intfstat', + 'scripts/lldpshow', + 'scripts/mmuconfig', + 'scripts/nbrshow', + 'scripts/neighbor_advertiser', + 'scripts/pcmping', + 'scripts/port2alias', + 'scripts/portconfig', + 'scripts/portstat', + 'scripts/pfcstat', + 'scripts/psushow', + 'scripts/queuestat', + 'scripts/reboot', + 'scripts/route_check.py', + 'scripts/route_check_test.sh', + 'scripts/sfpshow', + 'scripts/teamshow', + 'scripts/warm-reboot', + 'scripts/watermarkstat', + 'scripts/watermarkcfg' + ], + data_files=[ + ('/etc/bash_completion.d', glob.glob('data/etc/bash_completion.d/*')), + ], + entry_points={ + 'console_scripts': [ + 'acl-loader = acl_loader.main:cli', + 'config = config.main:config', + 'connect = connect.main:connect', + 'consutil = consutil.main:consutil', + 'counterpoll = counterpoll.main:cli', + 'crm = crm.main:cli', + 'debug = debug.main:cli', + 'pfcwd = pfcwd.main:cli', + 'sfputil = sfputil.main:cli', + 'pfc = pfc.main:cli', + 'psuutil = psuutil.main:cli', + 'ssdutil = ssdutil.main:cli', + 'show = show.main:cli', + 'sonic-clear = clear.main:cli', + 'sonic_installer = sonic_installer.main:cli', + 'undebug = undebug.main:cli', + ] + }, + # NOTE: sonic-utilities also depends on other packages that are either only + # available as .whl files or the latest available Debian packages are + # out-of-date and we must install newer versions via pip. These + # dependencies cannot be listed here, as this package is built as a .deb, + # therefore all dependencies will be assumed to also be available as .debs. + # These unlistable dependencies are as follows: + # - sonic-config-engine + # - swsssdk + # - tabulate + install_requires=[ + 'click-default-group', + 'click', + 'natsort' + ], + setup_requires= [ + 'pytest-runner' + ], + tests_require = [ + 'pytest', + 'mock>=2.0.0', + 'mockredispy>=2.9.3' + ], + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'Intended Audience :: System Administrators', + 'License :: OSI Approved :: Apache Software License', + 'Natural Language :: English', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python :: 2.7', + 'Topic :: Utilities', + ], + keywords='sonic SONiC utilities command line cli CLI', + test_suite='setup.get_test_suite' +) diff --git a/ssdutil/__init__.py b/ssdutil/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ssdutil/main.py b/ssdutil/main.py new file mode 100644 index 0000000..f49af76 --- /dev/null +++ b/ssdutil/main.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python +# +# main.py +# +# Command-line utility for interacting with ssd in SONiC +# + + + +try: + from ssdutil import SsdUtil + import imp + import syslog + import click + import os + import sys +except ImportError as e: + raise ImportError("%s - required module not found" % str(e)) + +VERSION = '1.0' + +VENDOR_PATH = "ssdvendors" + + +SYSLOG_IDENTIFIER = "ssdutil" + +# Global ssd vendor class instance +global ssd_vendor + + +# ========================== Syslog wrappers ========================== + + +def log_info(msg, also_print_to_console=False): + syslog.openlog(SYSLOG_IDENTIFIER) + syslog.syslog(syslog.LOG_INFO, msg) + syslog.closelog() + + if also_print_to_console: + click.echo(msg) + + +def log_warning(msg, also_print_to_console=False): + syslog.openlog(SYSLOG_IDENTIFIER) + syslog.syslog(syslog.LOG_WARNING, msg) + syslog.closelog() + + if also_print_to_console: + click.echo(msg) + + +def log_error(msg, also_print_to_console=False): + syslog.openlog(SYSLOG_IDENTIFIER) + syslog.syslog(syslog.LOG_ERR, msg) + syslog.closelog() + + if also_print_to_console: + click.echo(msg) + +# load ssd vendor module +def load_ssd_vendor(device): + global ssd_vendor + dvModel = SsdUtil().get_device_model(device) + if "InnoDisk" in dvModel: + try: + module_path = os.path.dirname(os.path.abspath(__file__)) + module_file = "/".join([module_path, VENDOR_PATH, "innodisk.py"]) + module = imp.load_source("innodisk", module_file) + except IOError, e: + log_error("Failed to load VENDOR module '%s': %s" % ("innodisk" , str(e)), True) + return -1 + try: + ssd_vendor_class = getattr(module, "InnoDisk") + ssd_vendor = ssd_vendor_class() + except AttributeError, e: + log_error("Failed to instantiate '%s' class: %s" % ("InnoDisk", str(e)), True) + return -2 + else: + pass + # extend the ssd vendor module here + + return 0 + + +# show ssd info + +def print_test_title(testname): + click.echo("{0:-^80s}".format("-")) + click.echo("{name: ^80s}".format(name=testname)) + click.echo("{0:-^80s}".format("-")) + + + + + +# ==================== CLI commands and groups ==================== + + +# This is our main entrypoint - the main 'ssdutil' command +@click.group() +def cli(): + """ssdutil - Command line utility for providing SSD info""" + if os.geteuid() != 0: + click.echo("Root privileges are required for this operation") + sys.exit(1) + + +# 'version' subcommand +@cli.command() +def version(): + """Display version info""" + click.echo("ssdutil version {0}".format(VERSION)) + + +# Vendor ssd info + +@cli.command() +@click.argument("device") +def peCycle(device): + """Show SSD P/E cycle""" + load_ssd_vendor(device) + pecycle = ssd_vendor.get_pecycle(device) + testname = "Show SSD P/E Cycle" + print_test_title(testname) + click.echo("Device P/E Cycle : {0}".format(pecycle)) + + +@cli.command() +@click.argument("device") +def health(device): + """Show SSD Health""" + load_ssd_vendor(device) + health = ssd_vendor.get_health(device) + testname = "Show SSD Health" + print_test_title(testname) + click.echo("Device Health : {:.1%}".format(health)) + + + +@cli.command() +@click.argument("device") +def remaintime(device): + """Show SSD Remaining Time""" + load_ssd_vendor(device) + rmtime = ssd_vendor.get_remain_time(device) + testname = "Show SSD Remaining Time" + print_test_title(testname) + click.echo("Device Remaining Time : {} Hours".format(rmtime)) + + + + +#['0', '0', '0', '0'] +# for 'Later_Bad_Block' and 'Later_Bad_Blk_Inf_R/W/E' +@cli.command() +@click.argument("device") +def badblock(device): + """Show SSD Bad Block""" + load_ssd_vendor(device) + infoList = ssd_vendor.get_bad_block(device) + testname = "Show SSD Bad Block" + print_test_title(testname) + Later_Bad_Block = infoList[0] + Later_Bad_Block_Read = infoList[1] + Later_Bad_Block_Write = infoList[2] + Later_Bad_Block_Erase = infoList[3] + click.echo("Device Later_Bad_Block : {}".format(Later_Bad_Block)) + click.echo("Device Later_Bad_Block Read : {}".format(Later_Bad_Block_Read)) + click.echo("Device Later_Bad_Block Write : {}".format(Later_Bad_Block_Write)) + click.echo("Device Later_Bad_Block Erase : {}".format(Later_Bad_Block_Erase)) + + + + + + + + +@cli.command() +@click.argument("device") +def temp(device): + """Show SSD Temperature""" + load_ssd_vendor(device) + temp = ssd_vendor.get_temp(device) + testname = "Show SSD Temperature" + print_test_title(testname) + click.echo("Device Temperature : {} C".format(temp)) + + +#common ssd info + +@cli.command() +@click.argument("device") +def device(device): + """Show SSD Device Model""" + dvModel = SsdUtil().get_device_model(device) + testname = "Show SSD Device Model" + print_test_title(testname) + click.echo("Device Model : {}".format(dvModel)) + + + + +@cli.command() +@click.argument("device") +def serialnum(device): + """Show SSD Serial Number""" + serialNum = SsdUtil().get_serial_num(device) + testname = "Show SSD Serial Number" + print_test_title(testname) + click.echo("Device Serial Number : {}".format(serialNum)) + + +@cli.command() +@click.argument("device") +def firmware(device): + """Show SSD Firmware Version""" + fwVersion = SsdUtil().get_firmware(device) + testname = "Show SSD Firmware Version" + print_test_title(testname) + click.echo("Device Firmware Version : {}".format(fwVersion)) + + +@cli.command() +@click.argument("device") +def capacity(device): + """Show SSD Capacity""" + capacity = SsdUtil().get_capacity(device) + testname = "Show SSD Capacity" + print_test_title(testname) + click.echo("Device Capacity : {}".format(capacity)) + + +if __name__ == '__main__': + cli() diff --git a/ssdutil/ssdutil.py b/ssdutil/ssdutil.py new file mode 100644 index 0000000..b2f3561 --- /dev/null +++ b/ssdutil/ssdutil.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python + + + + +# ssdutil.py +# platform-common SSD interface for SONIC + + + + + +try: + import os + import yaml + import subprocess +except ImportError as e: + raise ImportError("%s - required module not found" % str(e)) + + + +class SsdUtil(): + + def __init__(self): + pass + + # get ssd firmware version info + def get_firmware(self, device): + check = 0 + command = "sudo smartctl -i " + device + proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + output = proc.stdout.readlines() + (out, err) = proc.communicate() + if proc.returncode > 0: + for line in output: + print(line.strip()) + return + else: + for line in output: + if "Firmware Version" in line: + # Firmware Version: S17411 + check = 1 + fwVersion = line.split(':')[-1].strip() + if check == 0: + print("Can't get 'Firmware Version' attributes") + return + else: + return fwVersion + # S17411 + + + # get ssd serial number info + def get_serial_num(self, device): + check = 0 + command = "sudo smartctl -i " + device + proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + output = proc.stdout.readlines() + (out, err) = proc.communicate() + if proc.returncode > 0: + for line in output: + print(line.strip()) + return + else: + for line in output: + if "Serial Number" in line: + #Serial Number: BCA11709250230058 + check = 1 + serialNum = line.split(':')[-1].strip() + if check == 0: + print("Can't get 'Serial Number' attributes") + return + else: + return serialNum + # 'BCA11709250230058' + + + # get ssd device model info + def get_device_model(self, device): + check = 0 + command = "sudo smartctl -i " + device + proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + output = proc.stdout.readlines() + (out, err) = proc.communicate() + if proc.returncode > 0: + for line in output: + print(line.strip()) + return + else: + for line in output: + if "Device Model" in line: + #Device Model: InnoDisk Corp. - mSATA 3ME3 + check = 1 + dvModel = line.split(':')[-1].strip() + if check == 0: + print("Can't get 'Device Model' attributes") + return + else: + return dvModel + # 'InnoDisk Corp. - mSATA 3ME3' + + + + def get_capacity(self, device): + check = 0 + command = "sudo smartctl -i " + device + proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + output = proc.stdout.readlines() + (out, err) = proc.communicate() + if proc.returncode > 0: + for line in output: + print(line.strip()) + return + else: + for line in output: + if "User Capacity" in line: + # User Capacity: 16,013,942,784 bytes [16.0 GB] + check = 1 + capacity = line.split(':')[-1].strip() + if check == 0: + print("Can't get 'User Capacity' attributes") + return + else: + return capacity + # '16,013,942,784 bytes [16.0 GB]' + + + + + + + + + + + + + + + + + + + diff --git a/ssdutil/ssdvendors/__init__.py b/ssdutil/ssdvendors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ssdutil/ssdvendors/innodisk.py b/ssdutil/ssdvendors/innodisk.py new file mode 100644 index 0000000..11a8d1d --- /dev/null +++ b/ssdutil/ssdvendors/innodisk.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python + + +# SSD specific interface for Innodisk Vendor + +try: + import os + import yaml + import subprocess +except ImportError as e: + raise ImportError("%s - required module not found" % str(e)) + +class InnoDisk(): + def __init__(self): + pass + + # get ssd P/E cycle info + def get_pecycle(self, device): + check = 0 + command = "sudo smartctl -i " + device + proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + output = proc.stdout.readlines() + (out, err) = proc.communicate() + #smartctl cmd return failed + if proc.returncode > 0: + for line in output: + print(line.strip()) + return + else: + for line in output: + if "Device Model" in line: + check = 1 + if ("3ME3" in line) or ("3ME4" in line): + cycle = 3000 + elif ("3IE3" in line): + cycle = 20000 + else: + print(line.strip()) + print("Device Model Not Match 3ME3 3ME4 or 3IE3") + return + if check == 0: + print("Can't get 'Device Model' attributes") + return + else: + return cycle + #3000 + + # get ssd health info + def get_health(self, device): + check = 0 + # get the SSd P/E cycle + cycle = self.get_pecycle(device) + command = "sudo smartctl -A " + device + proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + output = proc.stdout.readlines() + (out, err) = proc.communicate() + #smartctl cmd return failed + if proc.returncode > 0: + for line in output: + print(line.strip()) + return + else: + for line in output: + if "Average_Erase_Count" in line: + # Average_Erase_Count 0x0002 048 001 000 Old_age Always - 48 + check = 1 + rawval = line.split()[-1] + avgerase = float(rawval) + #health_pre = (P/E cycle - AVG erese)/ P/E cycle + health= (cycle - avgerase) / cycle + health = round(health, 5) + if check == 0: + print("Can't get 'Device Model' attributes") + return + else: + return health + # 0.983 + + # get ssd remainning time info + def get_remain_time(self, device): + check = 0 + # get the SSD health + health = self.get_health(device) + command = "sudo smartctl -A " + device + proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + output = proc.stdout.readlines() + (out, err) = proc.communicate() + #smartctl cmd return failed + if proc.returncode > 0: + for line in output: + print(line.strip()) + return + else: + for line in output: + if "Power_On_Hours" in line: + # 9 Power_On_Hours 0x0002 080 000 000 Old_age Always - 2640 + check = 1 + rawval = line.split()[-1] + poweron = int(rawval) + #remain_time = poweron_time / (1 - health_pre) - poweron_time + remainTime = poweron / (1 - health ) - poweron + if check == 0: + print("Can't get 'Power_On_Hours' attributes") + return + else: + return int(remainTime) + # 12345 + + # get ssd badblock info + def get_bad_block(self, device): + check = 0 + _blockList = [] + command = "sudo smartctl -A " + device + proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + output = proc.stdout.readlines() + (out, err) = proc.communicate() + if proc.returncode > 0: + for line in output: + print(line.strip()) + return + else: + for line in output: + if "Later_Bad_Block" in line: + check += 1 + # Later_Bad_Block 0x0013 100 100 001 Pre-fail Always - 0 + rawval = line.split()[-1] + _blockList.append(rawval) + if "Later_Bad_Blk_Inf_R/W/E" in line: + check += 1 + # Later_Bad_Blk_Inf_R/W/E 0x0002 000 000 000 Old_age Always - 0 0 0 + rawval_read = line.split()[-3] + rawval_write = line.split()[-2] + rawval_erase = line.split()[-1] + _blockList.append(rawval_read) + _blockList.append(rawval_write) + _blockList.append(rawval_erase) + if check == 0: + print("Can't get any 'Later_Bad_Block' attributes") + return + elif check == 1: + print("Can't get all 'Later_Bad_Block' attributes") + return _blockList + else: + return _blockList + #['0', '0', '0', '0'] + # for 'Later_Bad_Block' and 'Later_Bad_Blk_Inf_R/W/E' + + # get ssd temperature info + def get_temp(self, device): + check = 0 + command = "sudo smartctl -A " + device + proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + output = proc.stdout.readlines() + (out, err) = proc.communicate() + if proc.returncode > 0: + for line in output: + print(line.strip()) + return + else: + for line in output: + if "Temperature_Celsius" in line: + # Temperature_Celsius 0x0000 030 100 000 Old_age Offline - 30 (2 100 0 0 0) + check = 1 + rawval = line.split()[9] + if check == 0: + print("Can't get all 'Temperature_Celsius' attributes") + return + else: + return rawval + # '30' + + + + + + + + + + + + + + + \ No newline at end of file