Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sonic-utils: initial support for link-training #2071

Merged
merged 5 commits into from
May 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3674,6 +3674,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 link training 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
66 changes: 66 additions & 0 deletions scripts/intfutil
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ PORT_INTERFACE_TYPE = 'interface_type'
PORT_ADV_INTERFACE_TYPES = 'adv_interface_types'
PORT_TPID = "tpid"
OPTICS_TYPE_RJ45 = 'RJ45'
PORT_LINK_TRAINING = 'link_training'
PORT_LINK_TRAINING_STATUS = 'link_training_status'

VLAN_SUB_INTERFACE_SEPARATOR = "."
VLAN_SUB_INTERFACE_TYPE = "802.1q-encapsulation"
Expand Down Expand Up @@ -739,6 +741,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_status_get(self.db, key, PORT_LINK_TRAINING_STATUS)
if lt_status in ['N/A', '', None]:
lt_status = 'off'
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 @@ -759,6 +822,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
18 changes: 17 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 @@ -131,6 +133,16 @@ 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))
lt_modes = ['on', 'off']
if mode not in lt_modes:
print('Invalid mode specified: {}'.format(mode))
print('Valid modes: {}'.format(','.join(lt_modes)))
exit(1)
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 @@ -263,6 +275,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 @@ -271,13 +285,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 ...)
Copy link
Contributor

@qiluo-msft qiluo-msft May 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

show interfaces

Is there a reason to choose show interfaces vs show interface? #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea, this is a legacy command, and my best guess is that it supports both signle interface and multi-interface at the same time, hence the original author adopted 'show interfaces' instead of 'show interface'

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you fix the PR description? I see many wrong subcommand.

sudo config interface link-training Ethernet0 on

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm not following you, is this about 'interfaces' v.s. 'interface' in show and config command?
If that's the case, this is a legacy command format since day 1, while the link-training is actually a plug-in to these legacy CLICK based CLI commands, hence it's not expected to change the upper level command syntax.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. I see the difference between show and config.

#
@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
37 changes: 19 additions & 18 deletions tests/dump_tests/dump_state_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,22 @@ def compare_json_output(exp_json, rec, exclude_paths=None):


table_display_output = '''\
+-------------+-----------+----------------------------------------------------------------------------+
| port_name | DB_NAME | DUMP |
+=============+===========+============================================================================+
| Ethernet0 | STATE_DB | +----------------------+-------------------------------------------------+ |
| | | | Keys | field-value pairs | |
| | | +======================+=================================================+ |
| | | | PORT_TABLE|Ethernet0 | +------------------+--------------------------+ | |
| | | | | | field | value | | |
| | | | | |------------------+--------------------------| | |
| | | | | | rmt_adv_speeds | 10,100,1000 | | |
| | | | | | speed | 100000 | | |
| | | | | | supported_speeds | 10000,25000,40000,100000 | | |
| | | | | +------------------+--------------------------+ | |
| | | +----------------------+-------------------------------------------------+ |
+-------------+-----------+----------------------------------------------------------------------------+
+-------------+-----------+--------------------------------------------------------------------------------+
| port_name | DB_NAME | DUMP |
+=============+===========+================================================================================+
| Ethernet0 | STATE_DB | +----------------------+-----------------------------------------------------+ |
| | | | Keys | field-value pairs | |
| | | +======================+=====================================================+ |
| | | | PORT_TABLE|Ethernet0 | +----------------------+--------------------------+ | |
| | | | | | field | value | | |
| | | | | |----------------------+--------------------------| | |
| | | | | | rmt_adv_speeds | 10,100,1000 | | |
| | | | | | speed | 100000 | | |
| | | | | | supported_speeds | 10000,25000,40000,100000 | | |
| | | | | | link_training_status | not_trained | | |
| | | | | +----------------------+--------------------------+ | |
| | | +----------------------+-----------------------------------------------------+ |
+-------------+-----------+--------------------------------------------------------------------------------+
'''


Expand Down Expand Up @@ -121,7 +122,7 @@ def test_identifier_single(self):
expected = {'Ethernet0': {'CONFIG_DB': {'keys': [{'PORT|Ethernet0': {'alias': 'etp1', 'description': 'etp1', 'index': '0', 'lanes': '25,26,27,28', 'mtu': '9100', 'pfc_asym': 'off', 'speed': '40000'}}], 'tables_not_found': []},
'APPL_DB': {'keys': [{'PORT_TABLE:Ethernet0': {'index': '0', 'lanes': '0', 'alias': 'Ethernet0', 'description': 'ARISTA01T2:Ethernet1', 'speed': '25000', 'oper_status': 'down', 'pfc_asym': 'off', 'mtu': '9100', 'fec': 'rs', 'admin_status': 'up'}}], 'tables_not_found': []},
'ASIC_DB': {'keys': [{'ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF:oid:0xd00000000056d': {'SAI_HOSTIF_ATTR_NAME': 'Ethernet0', 'SAI_HOSTIF_ATTR_OBJ_ID': 'oid:0x10000000004a4', 'SAI_HOSTIF_ATTR_OPER_STATUS': 'true', 'SAI_HOSTIF_ATTR_TYPE': 'SAI_HOSTIF_TYPE_NETDEV', 'SAI_HOSTIF_ATTR_VLAN_TAG': 'SAI_HOSTIF_VLAN_TAG_STRIP'}}, {'ASIC_STATE:SAI_OBJECT_TYPE_PORT:oid:0x10000000004a4': {'NULL': 'NULL', 'SAI_PORT_ATTR_ADMIN_STATE': 'true', 'SAI_PORT_ATTR_MTU': '9122', 'SAI_PORT_ATTR_SPEED': '100000'}}], 'tables_not_found': [], 'vidtorid': {'oid:0xd00000000056d': 'oid:0xd', 'oid:0x10000000004a4': 'oid:0x1690000000001'}},
'STATE_DB': {'keys': [{'PORT_TABLE|Ethernet0': {'rmt_adv_speeds': '10,100,1000', 'speed': '100000', 'supported_speeds': '10000,25000,40000,100000'}}], 'tables_not_found': []}}}
'STATE_DB': {'keys': [{'PORT_TABLE|Ethernet0': {'rmt_adv_speeds': '10,100,1000', 'speed': '100000', 'supported_speeds': '10000,25000,40000,100000', 'link_training_status': 'not_trained'}}], 'tables_not_found': []}}}

assert result.exit_code == 0, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
# Cause other tests depend and change these paths in the mock_db, this test would fail everytime when a field or a value in changed in this path, creating noise
Expand All @@ -138,7 +139,7 @@ def test_identifier_multiple(self):
{"CONFIG_DB": {"keys": [{"PORT|Ethernet0": {"alias": "etp1", "description": "etp1", "index": "0", "lanes": "25,26,27,28", "mtu": "9100", "pfc_asym": "off", "speed": "40000"}}], "tables_not_found": []},
"APPL_DB": {"keys": [{"PORT_TABLE:Ethernet0": {"index": "0", "lanes": "0", "alias": "Ethernet0", "description": "ARISTA01T2:Ethernet1", "speed": "25000", "oper_status": "down", "pfc_asym": "off", "mtu": "9100", "fec": "rs", "admin_status": "up"}}], "tables_not_found": []},
"ASIC_DB": {"keys": [{"ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF:oid:0xd00000000056d": {"SAI_HOSTIF_ATTR_NAME": "Ethernet0", "SAI_HOSTIF_ATTR_OBJ_ID": "oid:0x10000000004a4", "SAI_HOSTIF_ATTR_OPER_STATUS": "true", "SAI_HOSTIF_ATTR_TYPE": "SAI_HOSTIF_TYPE_NETDEV", "SAI_HOSTIF_ATTR_VLAN_TAG": "SAI_HOSTIF_VLAN_TAG_STRIP"}}, {"ASIC_STATE:SAI_OBJECT_TYPE_PORT:oid:0x10000000004a4": {"NULL": "NULL", "SAI_PORT_ATTR_ADMIN_STATE": "true", "SAI_PORT_ATTR_MTU": "9122", "SAI_PORT_ATTR_SPEED": "100000"}}], "tables_not_found": [], "vidtorid": {"oid:0xd00000000056d": "oid:0xd", "oid:0x10000000004a4": "oid:0x1690000000001"}},
"STATE_DB": {"keys": [{"PORT_TABLE|Ethernet0": {"rmt_adv_speeds": "10,100,1000", "speed": "100000", "supported_speeds": "10000,25000,40000,100000"}}], "tables_not_found": []}},
"STATE_DB": {"keys": [{"PORT_TABLE|Ethernet0": {"rmt_adv_speeds": "10,100,1000", "speed": "100000", "supported_speeds": "10000,25000,40000,100000", "link_training_status": "not_trained"}}], "tables_not_found": []}},
"Ethernet4":
{"CONFIG_DB": {"keys": [{"PORT|Ethernet4": {"admin_status": "up", "alias": "etp2", "description": "Servers0:eth0", "index": "1", "lanes": "29,30,31,32", "mtu": "9100", "pfc_asym": "off", "speed": "40000"}}], "tables_not_found": []},
"APPL_DB": {"keys": [], "tables_not_found": ["PORT_TABLE"]},
Expand Down Expand Up @@ -167,7 +168,7 @@ def test_option_db_filtering(self):
result = runner.invoke(dump.state, ["port", "Ethernet0", "--db", "ASIC_DB", "--db", "STATE_DB"])
print(result.output)
expected = {"Ethernet0": {"ASIC_DB": {"keys": [{"ASIC_STATE:SAI_OBJECT_TYPE_HOSTIF:oid:0xd00000000056d": {"SAI_HOSTIF_ATTR_NAME": "Ethernet0", "SAI_HOSTIF_ATTR_OBJ_ID": "oid:0x10000000004a4", "SAI_HOSTIF_ATTR_OPER_STATUS": "true", "SAI_HOSTIF_ATTR_TYPE": "SAI_HOSTIF_TYPE_NETDEV", "SAI_HOSTIF_ATTR_VLAN_TAG": "SAI_HOSTIF_VLAN_TAG_STRIP"}}, {"ASIC_STATE:SAI_OBJECT_TYPE_PORT:oid:0x10000000004a4": {"NULL": "NULL", "SAI_PORT_ATTR_ADMIN_STATE": "true", "SAI_PORT_ATTR_MTU": "9122", "SAI_PORT_ATTR_SPEED": "100000"}}], "tables_not_found": [], "vidtorid": {"oid:0xd00000000056d": "oid:0xd", "oid:0x10000000004a4": "oid:0x1690000000001"}},
"STATE_DB": {"keys": [{"PORT_TABLE|Ethernet0": {"rmt_adv_speeds": "10,100,1000", "speed": "100000", "supported_speeds": "10000,25000,40000,100000"}}], "tables_not_found": []}}}
"STATE_DB": {"keys": [{"PORT_TABLE|Ethernet0": {"rmt_adv_speeds": "10,100,1000", "speed": "100000", "supported_speeds": "10000,25000,40000,100000", "link_training_status": "not_trained"}}], "tables_not_found": []}}}
assert result.exit_code == 0, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
ddiff = compare_json_output(expected, result.output)
assert not ddiff, ddiff
Expand Down
Loading