diff --git a/acl_loader/main.py b/acl_loader/main.py index b17d647a8a..dbe61e3276 100644 --- a/acl_loader/main.py +++ b/acl_loader/main.py @@ -7,6 +7,7 @@ import json import argparse import tabulate +from natsort import natsorted import openconfig_acl import pyangbind.lib.pybindJSON as pybindJSON @@ -384,7 +385,7 @@ def show_table(self, table_name): if not val["ports"]: data.append([key, val["type"], "", val["policy_desc"]]) else: - ports = sorted(val["ports"], ) + ports = natsorted(val["ports"]) data.append([key, val["type"], ports[0], val["policy_desc"]]) if len(ports) > 1: diff --git a/config/main.py b/config/main.py old mode 100644 new mode 100755 index 7b3020c279..8c48f16c3d --- a/config/main.py +++ b/config/main.py @@ -193,6 +193,94 @@ def load_minigraph(): _restart_services() print "Please note setting loaded from minigraph will be lost after system reboot. To preserve setting, run `config save`." +# +# 'vlan' group +# +@cli.group() +@click.pass_context +@click.option('-s', '--redis-unix-socket-path', help='unix socket path for redis connection') +def vlan(ctx, redis_unix_socket_path): + """VLAN-related configuration tasks""" + 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) + ctx.obj = {'db': config_db} + pass + +@vlan.command('add') +@click.argument('vid', metavar='', required=True, type=int) +@click.pass_context +def add_vlan(ctx, vid): + db = ctx.obj['db'] + vlan = 'Vlan{}'.format(vid) + if len(db.get_entry('VLAN', vlan)) != 0: + print "{} already exists".format(vlan) + raise click.Abort + db.set_entry('VLAN', vlan, {'vlanid': vid}) + +@vlan.command('del') +@click.argument('vid', metavar='', required=True, type=int) +@click.pass_context +def del_vlan(ctx, vid): + db = ctx.obj['db'] + keys = [ (k, v) for k, v in db.get_table('VLAN_MEMBER') if k == 'Vlan{}'.format(vid) ] + for k in keys: + db.set_entry('VLAN_MEMBER', k, None) + db.set_entry('VLAN', 'Vlan{}'.format(vid), None) + +@vlan.group('member') +@click.pass_context +def vlan_member(ctx): + pass + +@vlan_member.command('add') +@click.argument('vid', metavar='', required=True, type=int) +@click.argument('interface_name', metavar='', required=True) +@click.option('-u', '--untagged', is_flag=True) +@click.pass_context +def add_vlan_member(ctx, vid, interface_name, untagged): + db = ctx.obj['db'] + vlan_name = 'Vlan{}'.format(vid) + vlan = db.get_entry('VLAN', vlan_name) + if len(vlan) == 0: + print "{} doesn't exist".format(vlan_name) + raise click.Abort + members = vlan.get('members', []) + if interface_name in members: + print "{} is already a member of {}".format(interface_name, vlan_name) + raise click.Abort + members.append(interface_name) + vlan['members'] = members + db.set_entry('VLAN', vlan_name, vlan) + db.set_entry('VLAN_MEMBER', (vlan_name, interface_name), {'tagging_mode': "untagged" if untagged else "tagged" }) + + +@vlan_member.command('del') +@click.argument('vid', metavar='', required=True, type=int) +@click.argument('interface_name', metavar='', required=True) +@click.pass_context +def del_vlan_member(ctx, vid, interface_name): + db = ctx.obj['db'] + vlan_name = 'Vlan{}'.format(vid) + vlan = db.get_entry('VLAN', vlan_name) + if len(vlan) == 0: + print "{} doesn't exist".format(vlan_name) + raise click.Abort + members = vlan.get('members', []) + if interface_name not in members: + print "{} is not a member of {}".format(interface_name, vlan_name) + raise click.Abort + members.remove(interface_name) + if len(members) == 0: + del vlan['members'] + else: + vlan['members'] = members + db.set_entry('VLAN', vlan_name, vlan) + db.set_entry('VLAN_MEMBER', (vlan_name, interface_name), None) + + # # 'bgp' group # diff --git a/scripts/interface_stat b/scripts/interface_stat deleted file mode 100755 index 434db777d1..0000000000 --- a/scripts/interface_stat +++ /dev/null @@ -1,58 +0,0 @@ -#! /usr/bin/python - -import swsssdk -import sys -import re -from tabulate import tabulate -from natsort import natsorted - -header = ['Iface', 'Lanes', 'Alias', 'Oper', 'Admin', 'MTU'] - - -PORT_STATUS_TABLE_PREFIX = "PORT_TABLE:" -PORT_LANES_STATUS_FIELD = "lanes" -PORT_ALIAS_STATUS_FIELD = "alias" -PORT_OPER_STATUS_FIELD = "oper_status" -PORT_ADMIN_STATUS_FIELD = "admin_status" -PORT_MTU_STATUS_FIELD = "mtu" - -class Intstat(object): - table = [] - - def get_port_status(self, port_name, status_type): - """ - Get the port status - """ - full_table_id = PORT_STATUS_TABLE_PREFIX + port_name - status = self.db.get(self.db.APPL_DB, full_table_id, status_type) - if status is None: - return "N/A" - else: - return status - - def __init__(self): - self.db = swsssdk.SonicV2Connector(host='127.0.0.1') - self.db.connect(self.db.APPL_DB) - a = self.db.keys(self.db.APPL_DB) - - tables = self.db.keys(self.db.APPL_DB, "PORT_TABLE:*") - i = {} - table = [] - key = [] - - for i in tables: - key = re.split(':', i, maxsplit=1)[-1].strip() - if key and key.startswith('Ethernet'): - table.append((key, self.get_port_status(key, PORT_LANES_STATUS_FIELD), self.get_port_status(key, PORT_ALIAS_STATUS_FIELD), self.get_port_status(key, PORT_OPER_STATUS_FIELD), self.get_port_status(key, PORT_ADMIN_STATUS_FIELD), self.get_port_status(key, PORT_MTU_STATUS_FIELD))) - - sorted_table = natsorted(table) - print tabulate(sorted_table, header, tablefmt="simple", stralign='right') - - -def main(): - interface_stat = Intstat() - - sys.exit(0) - -if __name__ == "__main__": - main() diff --git a/scripts/intfutil b/scripts/intfutil new file mode 100755 index 0000000000..33d418437f --- /dev/null +++ b/scripts/intfutil @@ -0,0 +1,178 @@ +#! /usr/bin/python + +import swsssdk +import sys +import re +from tabulate import tabulate +from natsort import natsorted + + + +# ========================== Common interface-utils logic ========================== + + +PORT_STATUS_TABLE_PREFIX = "PORT_TABLE:" +PORT_LANES_STATUS = "lanes" +PORT_ALIAS = "alias" +PORT_OPER_STATUS = "oper_status" +PORT_ADMIN_STATUS = "admin_status" +PORT_SPEED = "speed" +PORT_MTU_STATUS = "mtu" +PORT_DESCRIPTION = "description" + + +def db_connect(): + db = swsssdk.SonicV2Connector(host='127.0.0.1') + if db == None: + return None + + db.connect(db.APPL_DB) + + return db + + +def db_keys_get(db, intf_name): + + if intf_name == None: + db_keys = db.keys(db.APPL_DB, "PORT_TABLE:*") + elif intf_name.startswith('Ethernet'): + db_keys = db.keys(db.APPL_DB, "PORT_TABLE:%s" % intf_name) + else: + return None + + return db_keys + + +def db_port_status_get(db, intf_name, status_type): + """ + Get the port status + """ + + full_table_id = PORT_STATUS_TABLE_PREFIX + intf_name + status = db.get(db.APPL_DB, full_table_id, status_type) + if status is None: + return "N/A" + + if status_type == PORT_SPEED and status != "N/A": + status = '{}G'.format(status[:2] if int(status) < 100000 else status[:3]) + + return status + + +# ========================== interface-status logic ========================== + +header_stat = ['Interface', 'Lanes', 'Speed', 'MTU', 'Alias', 'Oper', 'Admin'] + +class IntfStatus(object): + + def display_intf_status(self, db_keys): + """ + Generate interface-status output + """ + + i = {} + table = [] + key = [] + + # + # Iterate through all the keys and append port's associated state to + # the result table. + # + for i in db_keys: + key = re.split(':', i, maxsplit=1)[-1].strip() + if key and key.startswith('Ethernet'): + table.append((key, + db_port_status_get(self.db, key, PORT_LANES_STATUS), + db_port_status_get(self.db, key, PORT_SPEED), + db_port_status_get(self.db, key, PORT_MTU_STATUS), + db_port_status_get(self.db, key, PORT_ALIAS), + db_port_status_get(self.db, key, PORT_OPER_STATUS), + db_port_status_get(self.db, key, PORT_ADMIN_STATUS))) + + # Sorting and tabulating the result table. + sorted_table = natsorted(table) + print tabulate(sorted_table, header_stat, tablefmt="simple", stralign='right') + + + def __init__(self, intf_name): + + self.db = db_connect() + if self.db == None: + return + + db_keys = db_keys_get(self.db, intf_name) + if db_keys == None: + return + + self.display_intf_status(db_keys) + + +# ========================== interface-description logic ========================== + + +header_desc = ['Interface', 'Oper', 'Admin', 'Alias', 'Description'] + + +class IntfDescription(object): + + def display_intf_description(self, db_keys): + """ + Generate interface-description output + """ + + i = {} + table = [] + key = [] + + # + # Iterate through all the keys and append port's associated state to + # the result table. + # + for i in db_keys: + key = re.split(':', i, maxsplit=1)[-1].strip() + if key and key.startswith('Ethernet'): + table.append((key, + db_port_status_get(self.db, key, PORT_OPER_STATUS), + db_port_status_get(self.db, key, PORT_ADMIN_STATUS), + db_port_status_get(self.db, key, PORT_ALIAS), + db_port_status_get(self.db, key, PORT_DESCRIPTION))) + + # Sorting and tabulating the result table. + sorted_table = natsorted(table) + print tabulate(sorted_table, header_desc, tablefmt="simple", stralign='right') + + def __init__(self, intf_name): + + self.db = db_connect() + if self.db == None: + return + + db_keys = db_keys_get(self.db, intf_name) + if db_keys == None: + return + + self.display_intf_description(db_keys) + + + +def main(args): + if len(args) == 0: + print "No valid arguments provided" + return + + command = args[0] + if command != "status" and command != "description": + print "No valid command provided" + return + + intf_name = args[1] if len(args) == 2 else None + + if command == "status": + interface_stat = IntfStatus(intf_name) + elif command == "description": + interface_desc = IntfDescription(intf_name) + + sys.exit(0) + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/setup.py b/setup.py index 42a96b404c..4daa12125a 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,17 @@ import glob +import sys from setuptools import setup import unittest +# For python3 wheel, we currently don't need test +# TODO: build python3 wheel of all the dependencies, and remove conditional test def get_test_suite(): - test_loader = unittest.TestLoader() - test_suite = test_loader.discover('sonic-utilities-tests', pattern='*.py') - return test_suite + if sys.version_info >= (3, 0): + return unittest.TestSuite() + else: + test_loader = unittest.TestLoader() + test_suite = test_loader.discover('sonic-utilities-tests', pattern='*.py') + return test_suite setup( name='sonic-utilities', @@ -46,7 +52,7 @@ def get_test_suite(): 'scripts/fast-reboot-dump.py', 'scripts/fdbshow', 'scripts/generate_dump', - 'scripts/interface_stat', + 'scripts/intfutil', 'scripts/lldpshow', 'scripts/port2alias', 'scripts/portstat', @@ -85,6 +91,7 @@ def get_test_suite(): 'Natural Language :: English', 'Operating System :: POSIX :: Linux', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.6', 'Topic :: Utilities', ], keywords='sonic SONiC utilities command line cli CLI', diff --git a/show/main.py b/show/main.py index 223f34bfea..02b6eba5c2 100755 --- a/show/main.py +++ b/show/main.py @@ -10,7 +10,7 @@ from click_default_group import DefaultGroup from natsort import natsorted from tabulate import tabulate -import swsssdk +from swsssdk import ConfigDBConnector try: # noinspection PyPep8Naming @@ -206,6 +206,7 @@ def alias(interfacename): click.echo(tabulate(body, header)) + # 'summary' subcommand ("show interfaces summary") @interfaces.command() @click.argument('interfacename', required=False) @@ -240,6 +241,7 @@ def basic(interfacename): run_command(command) + @transceiver.command() @click.argument('interfacename', required=False) def details(interfacename): @@ -252,15 +254,29 @@ def details(interfacename): run_command(command) + @interfaces.command() @click.argument('interfacename', required=False) def description(interfacename): """Show interface status, protocol and description""" if interfacename is not None: - command = "sudo vtysh -c 'show interface {}'".format(interfacename) + command = "intfutil description {}".format(interfacename) + else: + command = "intfutil description" + + run_command(command) + + +@interfaces.command() +@click.argument('interfacename', required=False) +def status(interfacename): + """Show Interface status information""" + + if interfacename is not None: + command = "intfutil status {}".format(interfacename) else: - command = "sudo vtysh -c 'show interface description'" + command = "intfutil status" run_command(command) @@ -291,11 +307,6 @@ def portchannel(): """Show PortChannel information""" run_command("teamshow") -@interfaces.command() -def status(): - """Show Interface status information""" - run_command("interface_stat") - # # 'mac' command ("show mac ...") @@ -494,7 +505,10 @@ def logging(process, lines, follow): if follow: run_command("sudo tail -f /var/log/syslog") else: - command = "sudo cat /var/log/syslog" + if os.path.isfile("/var/log/syslog.1"): + command = "sudo cat /var/log/syslog.1 /var/log/syslog" + else: + command = "sudo cat /var/log/syslog" if process is not None: command += " | grep '{}'".format(process) @@ -722,6 +736,32 @@ def id(bridge_name): command="sudo brctl showmacs {}".format(bridge_name) run_command(command) +@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 mode(key, data): + info = [] + for m in data.get('members', []): + entry = config_db.get_entry('VLAN_MEMBER', (key, m)) + mode = entry.get('tagging_mode') + if mode == None: + info.append('?') + else: + info.append(mode) + return '\n'.join(info) + + header = ['Name', 'VID', 'Member', 'Mode'] + click.echo(tabulate([ [k, data[k]['vlanid'], '\n'.join(data[k].get('members', [])), mode(k, data[k])] for k in keys ], header)) + + @cli.command('services') def services(): """Show all daemon services""" @@ -741,7 +781,7 @@ def services(): @cli.command() def aaa(): """Show AAA configuration in ConfigDb""" - config_db = swsssdk.ConfigDBConnector() + config_db = ConfigDBConnector() config_db.connect() data = config_db.get_table('AAA') output = '' @@ -756,7 +796,7 @@ def aaa(): @cli.command() def tacacs(): """Show TACACS+ configuration""" - config_db = swsssdk.ConfigDBConnector() + config_db = ConfigDBConnector() config_db.connect() output = '' data = config_db.get_table('TACPLUS') diff --git a/sonic_psu/psu_base.py b/sonic_psu/psu_base.py index fe5c3974a6..2e2204298e 100644 --- a/sonic_psu/psu_base.py +++ b/sonic_psu/psu_base.py @@ -8,7 +8,7 @@ try: import abc -except ImportError, e: +except ImportError as e: raise ImportError (str(e) + " - required module not found") class PsuBase(object):