Skip to content

Commit

Permalink
sonic-utils: initial support for link-training
Browse files Browse the repository at this point in the history
- What I did
Add CLI support for link-trainig

- How I did it
portconfig: initial support for link-training config
config/main.py: initial support for link-training config
intfutil: initial support for link-training show command
show/interfaces/init.py: initial support for link-training show command

- How to verify it
Manual test
Ran the Unit-tests to the corresponding changes

Signed-off-by: Dante Su <dante.su@broadcom.com>
  • Loading branch information
ds952811 committed Mar 24, 2022
1 parent f71ef64 commit 188c561
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 1 deletion.
30 changes: 30 additions & 0 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3427,6 +3427,36 @@ def speed(ctx, interface_name, interface_speed, verbose):
command += " -vv"
clicommon.run_command(command, display_cmd=verbose)

#
# 'link-training' subcommand
#

@interface.command()
@click.pass_context
@click.argument('interface_name', metavar='<interface_name>', required=True)
@click.argument('mode', metavar='<mode>', required=True, type=click.Choice(["on", "off"]))
@click.option('-v', '--verbose', is_flag=True, help="Enable verbose output")
def link_training(ctx, interface_name, mode, verbose):
"""Set interface auto negotiation mode"""
# Get the config_db connector
config_db = ctx.obj['config_db']

if clicommon.get_interface_naming_mode() == "alias":
interface_name = interface_alias_to_name(config_db, interface_name)
if interface_name is None:
ctx.fail("'interface_name' is None!")

log.log_info("'interface link-training {} {}' executing...".format(interface_name, mode))

if ctx.obj['namespace'] is DEFAULT_NAMESPACE:
command = "portconfig -p {} -lt {}".format(interface_name, mode)
else:
command = "portconfig -p {} -lt {} -n {}".format(interface_name, mode, ctx.obj['namespace'])

if verbose:
command += " -vv"
clicommon.run_command(command, display_cmd=verbose)

#
# 'autoneg' subcommand
#
Expand Down
77 changes: 77 additions & 0 deletions scripts/intfutil
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ from utilities_common import constants
from utilities_common import multi_asic as multi_asic_util
from utilities_common.intf_filter import parse_interface_in_filter
from sonic_py_common.interface import get_intf_longname
from swsscommon import swsscommon
from sonic_py_common import daemon_base

# mock the redis for unit test purposes #
try:
Expand All @@ -33,6 +35,7 @@ except KeyError:
PORT_STATUS_TABLE_PREFIX = "PORT_TABLE:"
PORT_STATE_TABLE_PREFIX = "PORT_TABLE|"
PORT_TRANSCEIVER_TABLE_PREFIX = "TRANSCEIVER_INFO|"
PORT_LINK_TRAINING_STATUS_TABLE_PREFIX = "LINK_TRAINING|"
PORT_LANES_STATUS = "lanes"
PORT_ALIAS = "alias"
PORT_OPER_STATUS = "oper_status"
Expand All @@ -48,6 +51,8 @@ PORT_ADV_SPEEDS = 'adv_speeds'
PORT_INTERFACE_TYPE = 'interface_type'
PORT_ADV_INTERFACE_TYPES = 'adv_interface_types'
PORT_TPID = "tpid"
PORT_LINK_TRAINING = 'link_training'
PORT_LINK_TRAINING_STATUS = 'status'

VLAN_SUB_INTERFACE_SEPARATOR = "."
VLAN_SUB_INTERFACE_TYPE = "802.1q-encapsulation"
Expand Down Expand Up @@ -178,6 +183,14 @@ def state_db_port_optics_get(state_db, intf_name, type):
return "N/A"
return optics_type

def state_db_port_link_training_status_get(state_db, intf_name, type):
"""
Get link training status for port
"""
full_table_id = PORT_LINK_TRAINING_STATUS_TABLE_PREFIX + intf_name
status = state_db.get(state_db.STATE_DB, full_table_id, type)
return "N/A" if status is None else status

def merge_dicts(x,y):
# store a copy of x, but overwrite with y's values where applicable
merged = dict(x,**y)
Expand Down Expand Up @@ -695,6 +708,67 @@ class IntfTpid(object):
self.table += self.generate_intf_tpid()


# ========================== interface-link-training logic ==========================
header_link_training = ['Interface', 'LT Oper', 'LT Admin', 'Oper', 'Admin']

class IntfLinkTrainingStatus(object):

def __init__(self, intf_name, namespace_option, display_option):
self.db = None
self.config_db = None
self.table = []
self.multi_asic = multi_asic_util.MultiAsic(
display_option, namespace_option)

if intf_name is not None and intf_name == SUB_PORT:
self.intf_name = None
else:
self.intf_name = intf_name

def display_link_training_status(self):
self.get_intf_link_training_status()
# Sorting and tabulating the result table.
sorted_table = natsorted(self.table)
print(tabulate(sorted_table, header_link_training, tablefmt="simple", stralign='right'))

@multi_asic_util.run_on_multi_asic
def get_intf_link_training_status(self):
self.front_panel_ports_list = get_frontpanel_port_list(self.config_db)
self.appl_db_keys = appl_db_keys_get(self.db, self.front_panel_ports_list, self.intf_name)
if self.appl_db_keys:
self.table += self.generate_link_training_status()

def generate_link_training_status(self):
"""
Generate interface-link-training output
"""

i = {}
table = []
key = []

#
# Iterate through all the keys and append port's associated state to
# the result table.
#
for i in self.appl_db_keys:
key = re.split(':', i, maxsplit=1)[-1].strip()
if key in self.front_panel_ports_list:
if self.multi_asic.skip_display(constants.PORT_OBJ, key):
continue
lt_admin = appl_db_port_status_get(self.db, key, PORT_LINK_TRAINING)
if lt_admin not in ['on', 'off']:
lt_admin = '-'
lt_status = state_db_port_link_training_status_get(self.db, key, PORT_LINK_TRAINING_STATUS)
if lt_status in ['N/A', '']:
lt_status = '-'
table.append((key,
lt_status.replace('_', ' '),
lt_admin,
appl_db_port_status_get(self.db, key, PORT_OPER_STATUS),
appl_db_port_status_get(self.db, key, PORT_ADMIN_STATUS)))
return table

def main():
parser = argparse.ArgumentParser(description='Display Interface information',
formatter_class=argparse.RawTextHelpFormatter)
Expand All @@ -715,6 +789,9 @@ def main():
elif args.command == "tpid":
interface_tpid = IntfTpid(args.interface, args.namespace, args.display)
interface_tpid.display_intf_tpid()
elif args.command == "link_training":
interface_lt_status = IntfLinkTrainingStatus(args.interface, args.namespace, args.display)
interface_lt_status.display_link_training_status()

sys.exit(0)

Expand Down
15 changes: 14 additions & 1 deletion scripts/portconfig
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ optional arguments:
-S --adv-speeds port advertised speeds
-t --interface-type port interface type
-T --adv-interface-types port advertised interface types
-lt --link-training port link training mode
"""
import os
import sys
Expand Down Expand Up @@ -49,6 +50,7 @@ PORT_AUTONEG_CONFIG_FIELD_NAME = "autoneg"
PORT_ADV_SPEEDS_CONFIG_FIELD_NAME = "adv_speeds"
PORT_INTERFACE_TYPE_CONFIG_FIELD_NAME = "interface_type"
PORT_ADV_INTERFACE_TYPES_CONFIG_FIELD_NAME = "adv_interface_types"
PORT_LINK_TRAINING_CONFIG_FIELD_NAME = "link_training"
PORT_CHANNEL_TABLE_NAME = "PORTCHANNEL"
PORT_CHANNEL_MBR_TABLE_NAME = "PORTCHANNEL_MEMBER"
TPID_CONFIG_FIELD_NAME = "tpid"
Expand Down Expand Up @@ -127,6 +129,13 @@ class portconfig(object):
print("Setting mtu %s on port %s" % (mtu, port))
self.db.mod_entry(PORT_TABLE_NAME, port, {PORT_MTU_CONFIG_FIELD_NAME: mtu})

def set_link_training(self, port, mode):
if self.verbose:
print("Setting link-training %s on port %s" % (mode, port))
if mode not in ['on', 'off']:
mode = 'off'
self.db.mod_entry(PORT_TABLE_NAME, port, {PORT_LINK_TRAINING_CONFIG_FIELD_NAME: mode})

def set_autoneg(self, port, mode):
if self.verbose:
print("Setting autoneg %s on port %s" % (mode, port))
Expand Down Expand Up @@ -253,6 +262,8 @@ def main():
help = 'port interface type', default=None)
parser.add_argument('-T', '--adv-interface-types', type = str, required = False,
help = 'port advertised interface types', default=None)
parser.add_argument('-lt', '--link-training', type = str, required = False,
help = 'port link training mode', default=None)
args = parser.parse_args()

# Load database config files
Expand All @@ -261,13 +272,15 @@ def main():
port = portconfig(args.verbose, args.port, args.namespace)
if args.list:
port.list_params(args.port)
elif args.speed or args.fec or args.mtu or args.autoneg or args.adv_speeds or args.interface_type or args.adv_interface_types or args.tpid:
elif args.speed or args.fec or args.mtu or args.link_training or args.autoneg or args.adv_speeds or args.interface_type or args.adv_interface_types or args.tpid:
if args.speed:
port.set_speed(args.port, args.speed)
if args.fec:
port.set_fec(args.port, args.fec)
if args.mtu:
port.set_mtu(args.port, args.mtu)
if args.link_training:
port.set_link_training(args.port, args.link_training)
if args.autoneg:
port.set_autoneg(args.port, args.autoneg)
if args.adv_speeds:
Expand Down
33 changes: 33 additions & 0 deletions show/interfaces/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -639,3 +639,36 @@ def autoneg_status(interfacename, namespace, display, verbose):
cmd += " -n {}".format(namespace)

clicommon.run_command(cmd, display_cmd=verbose)

#
# link-training group (show interfaces link-training ...)
#
@interfaces.group(name='link-training', cls=clicommon.AliasedGroup)
def link_training():
"""Show interface link-training information"""
pass

# 'link-training status' subcommand ("show interfaces link-training status")
@link_training.command(name='status')
@click.argument('interfacename', required=False)
@multi_asic_util.multi_asic_click_options
@click.option('--verbose', is_flag=True, help="Enable verbose output")
def link_training_status(interfacename, namespace, display, verbose):
"""Show interface link-training status"""

ctx = click.get_current_context()

cmd = "intfutil -c link_training"

#ignore the display option when interface name is passed
if interfacename is not None:
interfacename = try_convert_interfacename_from_alias(ctx, interfacename)

cmd += " -i {}".format(interfacename)
else:
cmd += " -d {}".format(display)

if namespace is not None:
cmd += " -n {}".format(namespace)

clicommon.run_command(cmd, display_cmd=verbose)
43 changes: 43 additions & 0 deletions tests/config_lt_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import click
import config.main as config
import operator
import os
import pytest
import sys

from click.testing import CliRunner
from utilities_common.db import Db

test_path = os.path.dirname(os.path.abspath(__file__))
modules_path = os.path.dirname(test_path)
scripts_path = os.path.join(modules_path, "scripts")
sys.path.insert(0, modules_path)


@pytest.fixture(scope='module')
def ctx(scope='module'):
db = Db()
obj = {'config_db':db.cfgdb, 'namespace': ''}
yield obj


class TestConfigInterface(object):
@classmethod
def setup_class(cls):
print("SETUP")
os.environ["PATH"] += os.pathsep + scripts_path
os.environ["UTILITIES_UNIT_TESTING"] = "1"

def test_config_link_training(self, ctx):
self.basic_check("link-training", ["Ethernet0", "on"], ctx)
self.basic_check("link-training", ["Ethernet0", "off"], ctx)
self.basic_check("link-training", ["Invalid", "on"], ctx, operator.ne)
self.basic_check("link-training", ["Invalid", "off"], ctx, operator.ne)
self.basic_check("link-training", ["Ethernet0", "invalid"], ctx, operator.ne)

def basic_check(self, command_name, para_list, ctx, op=operator.eq, expect_result=0):
runner = CliRunner()
result = runner.invoke(config.config.commands["interface"].commands[command_name], para_list, obj = ctx)
print(result.output)
assert op(result.exit_code, expect_result)
return result

0 comments on commit 188c561

Please sign in to comment.