Skip to content

Commit 88286cb

Browse files
authored
sonic-utils: initial support for link-training (sonic-net#2071)
HLD: sonic-net/SONiC#925 #### What I did Add CLI support for link-trainig #### How I did it 1. portconfig: initial support for link-training config 2. config/main.py: initial support for link-training config 3. intfutil: initial support for link-training show command 4. show/interfaces/__init__.py: initial support for link-training show command #### How to verify it 1. Manual test 2. Ran the Unit-tests to the corresponding changes #### Previous command output (if the output of a command-line utility has changed) #### New command output (if the output of a command-line utility has changed) ``` admin@sonic:~$ sudo config interface link-training Ethernet0 on admin@sonic:~$ sudo config interface link-training Ethernet8 on admin@sonic:~$ sudo config interface link-training Ethernet16 off admin@sonic:~$ sudo config interface link-training Ethernet24 on admin@sonic:~$ sudo config interface link-training Ethernet32 off admin@sonic:~$ show interfaces link-training status Interface LT Oper LT Admin Oper Admin ----------- ----------- ---------- ------ ------- Ethernet0 trained on up up Ethernet8 trained on up up Ethernet16 off off down up Ethernet24 not trained on down up Ethernet32 off off down up Ethernet40 off - down up Ethernet48 off - down up Ethernet56 off - down up Ethernet64 off - down up Ethernet72 off - down up Ethernet80 off - down up Ethernet88 off - down up Ethernet96 off - down up Ethernet104 off - down up Ethernet112 off - down up Ethernet120 off - down up Ethernet128 off - down up Ethernet136 off - down up Ethernet144 off - down up Ethernet152 off - down up Ethernet160 off - down up Ethernet168 off - down up Ethernet176 off - down up Ethernet184 off - down up Ethernet192 off - down up Ethernet200 off - down up Ethernet208 off - down up Ethernet216 off - down up Ethernet224 off - down up Ethernet232 off - down up Ethernet240 off - down up Ethernet248 off - down up admin@sonic:~$ ```
1 parent 29503ab commit 88286cb

File tree

9 files changed

+251
-25
lines changed

9 files changed

+251
-25
lines changed

config/main.py

+30
Original file line numberDiff line numberDiff line change
@@ -3692,6 +3692,36 @@ def speed(ctx, interface_name, interface_speed, verbose):
36923692
command += " -vv"
36933693
clicommon.run_command(command, display_cmd=verbose)
36943694

3695+
#
3696+
# 'link-training' subcommand
3697+
#
3698+
3699+
@interface.command()
3700+
@click.pass_context
3701+
@click.argument('interface_name', metavar='<interface_name>', required=True)
3702+
@click.argument('mode', metavar='<mode>', required=True, type=click.Choice(["on", "off"]))
3703+
@click.option('-v', '--verbose', is_flag=True, help="Enable verbose output")
3704+
def link_training(ctx, interface_name, mode, verbose):
3705+
"""Set interface link training mode"""
3706+
# Get the config_db connector
3707+
config_db = ctx.obj['config_db']
3708+
3709+
if clicommon.get_interface_naming_mode() == "alias":
3710+
interface_name = interface_alias_to_name(config_db, interface_name)
3711+
if interface_name is None:
3712+
ctx.fail("'interface_name' is None!")
3713+
3714+
log.log_info("'interface link-training {} {}' executing...".format(interface_name, mode))
3715+
3716+
if ctx.obj['namespace'] is DEFAULT_NAMESPACE:
3717+
command = "portconfig -p {} -lt {}".format(interface_name, mode)
3718+
else:
3719+
command = "portconfig -p {} -lt {} -n {}".format(interface_name, mode, ctx.obj['namespace'])
3720+
3721+
if verbose:
3722+
command += " -vv"
3723+
clicommon.run_command(command, display_cmd=verbose)
3724+
36953725
#
36963726
# 'autoneg' subcommand
36973727
#

scripts/intfutil

+66
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ PORT_INTERFACE_TYPE = 'interface_type'
5050
PORT_ADV_INTERFACE_TYPES = 'adv_interface_types'
5151
PORT_TPID = "tpid"
5252
OPTICS_TYPE_RJ45 = 'RJ45'
53+
PORT_LINK_TRAINING = 'link_training'
54+
PORT_LINK_TRAINING_STATUS = 'link_training_status'
5355

5456
VLAN_SUB_INTERFACE_SEPARATOR = "."
5557
VLAN_SUB_INTERFACE_TYPE = "802.1q-encapsulation"
@@ -739,6 +741,67 @@ class IntfTpid(object):
739741
self.table += self.generate_intf_tpid()
740742

741743

744+
# ========================== interface-link-training logic ==========================
745+
header_link_training = ['Interface', 'LT Oper', 'LT Admin', 'Oper', 'Admin']
746+
747+
class IntfLinkTrainingStatus(object):
748+
749+
def __init__(self, intf_name, namespace_option, display_option):
750+
self.db = None
751+
self.config_db = None
752+
self.table = []
753+
self.multi_asic = multi_asic_util.MultiAsic(
754+
display_option, namespace_option)
755+
756+
if intf_name is not None and intf_name == SUB_PORT:
757+
self.intf_name = None
758+
else:
759+
self.intf_name = intf_name
760+
761+
def display_link_training_status(self):
762+
self.get_intf_link_training_status()
763+
# Sorting and tabulating the result table.
764+
sorted_table = natsorted(self.table)
765+
print(tabulate(sorted_table, header_link_training, tablefmt="simple", stralign='right'))
766+
767+
@multi_asic_util.run_on_multi_asic
768+
def get_intf_link_training_status(self):
769+
self.front_panel_ports_list = get_frontpanel_port_list(self.config_db)
770+
self.appl_db_keys = appl_db_keys_get(self.db, self.front_panel_ports_list, self.intf_name)
771+
if self.appl_db_keys:
772+
self.table += self.generate_link_training_status()
773+
774+
def generate_link_training_status(self):
775+
"""
776+
Generate interface-link-training output
777+
"""
778+
779+
i = {}
780+
table = []
781+
key = []
782+
783+
#
784+
# Iterate through all the keys and append port's associated state to
785+
# the result table.
786+
#
787+
for i in self.appl_db_keys:
788+
key = re.split(':', i, maxsplit=1)[-1].strip()
789+
if key in self.front_panel_ports_list:
790+
if self.multi_asic.skip_display(constants.PORT_OBJ, key):
791+
continue
792+
lt_admin = appl_db_port_status_get(self.db, key, PORT_LINK_TRAINING)
793+
if lt_admin not in ['on', 'off']:
794+
lt_admin = '-'
795+
lt_status = state_db_port_status_get(self.db, key, PORT_LINK_TRAINING_STATUS)
796+
if lt_status in ['N/A', '', None]:
797+
lt_status = 'off'
798+
table.append((key,
799+
lt_status.replace('_', ' '),
800+
lt_admin,
801+
appl_db_port_status_get(self.db, key, PORT_OPER_STATUS),
802+
appl_db_port_status_get(self.db, key, PORT_ADMIN_STATUS)))
803+
return table
804+
742805
def main():
743806
parser = argparse.ArgumentParser(description='Display Interface information',
744807
formatter_class=argparse.RawTextHelpFormatter)
@@ -759,6 +822,9 @@ def main():
759822
elif args.command == "tpid":
760823
interface_tpid = IntfTpid(args.interface, args.namespace, args.display)
761824
interface_tpid.display_intf_tpid()
825+
elif args.command == "link_training":
826+
interface_lt_status = IntfLinkTrainingStatus(args.interface, args.namespace, args.display)
827+
interface_lt_status.display_link_training_status()
762828

763829
sys.exit(0)
764830

scripts/portconfig

+17-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ optional arguments:
2121
-S --adv-speeds port advertised speeds
2222
-t --interface-type port interface type
2323
-T --adv-interface-types port advertised interface types
24+
-lt --link-training port link training mode
2425
"""
2526
import os
2627
import sys
@@ -49,6 +50,7 @@ PORT_AUTONEG_CONFIG_FIELD_NAME = "autoneg"
4950
PORT_ADV_SPEEDS_CONFIG_FIELD_NAME = "adv_speeds"
5051
PORT_INTERFACE_TYPE_CONFIG_FIELD_NAME = "interface_type"
5152
PORT_ADV_INTERFACE_TYPES_CONFIG_FIELD_NAME = "adv_interface_types"
53+
PORT_LINK_TRAINING_CONFIG_FIELD_NAME = "link_training"
5254
PORT_CHANNEL_TABLE_NAME = "PORTCHANNEL"
5355
PORT_CHANNEL_MBR_TABLE_NAME = "PORTCHANNEL_MEMBER"
5456
TPID_CONFIG_FIELD_NAME = "tpid"
@@ -131,6 +133,16 @@ class portconfig(object):
131133
print("Setting mtu %s on port %s" % (mtu, port))
132134
self.db.mod_entry(PORT_TABLE_NAME, port, {PORT_MTU_CONFIG_FIELD_NAME: mtu})
133135

136+
def set_link_training(self, port, mode):
137+
if self.verbose:
138+
print("Setting link-training %s on port %s" % (mode, port))
139+
lt_modes = ['on', 'off']
140+
if mode not in lt_modes:
141+
print('Invalid mode specified: {}'.format(mode))
142+
print('Valid modes: {}'.format(','.join(lt_modes)))
143+
exit(1)
144+
self.db.mod_entry(PORT_TABLE_NAME, port, {PORT_LINK_TRAINING_CONFIG_FIELD_NAME: mode})
145+
134146
def set_autoneg(self, port, mode):
135147
if self.verbose:
136148
print("Setting autoneg %s on port %s" % (mode, port))
@@ -263,6 +275,8 @@ def main():
263275
help = 'port interface type', default=None)
264276
parser.add_argument('-T', '--adv-interface-types', type = str, required = False,
265277
help = 'port advertised interface types', default=None)
278+
parser.add_argument('-lt', '--link-training', type = str, required = False,
279+
help = 'port link training mode', default=None)
266280
args = parser.parse_args()
267281

268282
# Load database config files
@@ -271,13 +285,15 @@ def main():
271285
port = portconfig(args.verbose, args.port, args.namespace)
272286
if args.list:
273287
port.list_params(args.port)
274-
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:
288+
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:
275289
if args.speed:
276290
port.set_speed(args.port, args.speed)
277291
if args.fec:
278292
port.set_fec(args.port, args.fec)
279293
if args.mtu:
280294
port.set_mtu(args.port, args.mtu)
295+
if args.link_training:
296+
port.set_link_training(args.port, args.link_training)
281297
if args.autoneg:
282298
port.set_autoneg(args.port, args.autoneg)
283299
if args.adv_speeds:

show/interfaces/__init__.py

+33
Original file line numberDiff line numberDiff line change
@@ -639,3 +639,36 @@ def autoneg_status(interfacename, namespace, display, verbose):
639639
cmd += " -n {}".format(namespace)
640640

641641
clicommon.run_command(cmd, display_cmd=verbose)
642+
643+
#
644+
# link-training group (show interfaces link-training ...)
645+
#
646+
@interfaces.group(name='link-training', cls=clicommon.AliasedGroup)
647+
def link_training():
648+
"""Show interface link-training information"""
649+
pass
650+
651+
# 'link-training status' subcommand ("show interfaces link-training status")
652+
@link_training.command(name='status')
653+
@click.argument('interfacename', required=False)
654+
@multi_asic_util.multi_asic_click_options
655+
@click.option('--verbose', is_flag=True, help="Enable verbose output")
656+
def link_training_status(interfacename, namespace, display, verbose):
657+
"""Show interface link-training status"""
658+
659+
ctx = click.get_current_context()
660+
661+
cmd = "intfutil -c link_training"
662+
663+
#ignore the display option when interface name is passed
664+
if interfacename is not None:
665+
interfacename = try_convert_interfacename_from_alias(ctx, interfacename)
666+
667+
cmd += " -i {}".format(interfacename)
668+
else:
669+
cmd += " -d {}".format(display)
670+
671+
if namespace is not None:
672+
cmd += " -n {}".format(namespace)
673+
674+
clicommon.run_command(cmd, display_cmd=verbose)

tests/config_lt_test.py

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import click
2+
import config.main as config
3+
import operator
4+
import os
5+
import pytest
6+
import sys
7+
8+
from click.testing import CliRunner
9+
from utilities_common.db import Db
10+
11+
test_path = os.path.dirname(os.path.abspath(__file__))
12+
modules_path = os.path.dirname(test_path)
13+
scripts_path = os.path.join(modules_path, "scripts")
14+
sys.path.insert(0, modules_path)
15+
16+
17+
@pytest.fixture(scope='module')
18+
def ctx(scope='module'):
19+
db = Db()
20+
obj = {'config_db':db.cfgdb, 'namespace': ''}
21+
yield obj
22+
23+
24+
class TestConfigInterface(object):
25+
@classmethod
26+
def setup_class(cls):
27+
print("SETUP")
28+
os.environ["PATH"] += os.pathsep + scripts_path
29+
os.environ["UTILITIES_UNIT_TESTING"] = "1"
30+
31+
def test_config_link_training(self, ctx):
32+
self.basic_check("link-training", ["Ethernet0", "on"], ctx)
33+
self.basic_check("link-training", ["Ethernet0", "off"], ctx)
34+
self.basic_check("link-training", ["Invalid", "on"], ctx, operator.ne)
35+
self.basic_check("link-training", ["Invalid", "off"], ctx, operator.ne)
36+
self.basic_check("link-training", ["Ethernet0", "invalid"], ctx, operator.ne)
37+
38+
def basic_check(self, command_name, para_list, ctx, op=operator.eq, expect_result=0):
39+
runner = CliRunner()
40+
result = runner.invoke(config.config.commands["interface"].commands[command_name], para_list, obj = ctx)
41+
print(result.output)
42+
assert op(result.exit_code, expect_result)
43+
return result

tests/dump_tests/dump_state_test.py

+19-18
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,22 @@ def compare_json_output(exp_json, rec, exclude_paths=None):
2525

2626

2727
table_display_output = '''\
28-
+-------------+-----------+----------------------------------------------------------------------------+
29-
| port_name | DB_NAME | DUMP |
30-
+=============+===========+============================================================================+
31-
| Ethernet0 | STATE_DB | +----------------------+-------------------------------------------------+ |
32-
| | | | Keys | field-value pairs | |
33-
| | | +======================+=================================================+ |
34-
| | | | PORT_TABLE|Ethernet0 | +------------------+--------------------------+ | |
35-
| | | | | | field | value | | |
36-
| | | | | |------------------+--------------------------| | |
37-
| | | | | | rmt_adv_speeds | 10,100,1000 | | |
38-
| | | | | | speed | 100000 | | |
39-
| | | | | | supported_speeds | 10000,25000,40000,100000 | | |
40-
| | | | | +------------------+--------------------------+ | |
41-
| | | +----------------------+-------------------------------------------------+ |
42-
+-------------+-----------+----------------------------------------------------------------------------+
28+
+-------------+-----------+--------------------------------------------------------------------------------+
29+
| port_name | DB_NAME | DUMP |
30+
+=============+===========+================================================================================+
31+
| Ethernet0 | STATE_DB | +----------------------+-----------------------------------------------------+ |
32+
| | | | Keys | field-value pairs | |
33+
| | | +======================+=====================================================+ |
34+
| | | | PORT_TABLE|Ethernet0 | +----------------------+--------------------------+ | |
35+
| | | | | | field | value | | |
36+
| | | | | |----------------------+--------------------------| | |
37+
| | | | | | rmt_adv_speeds | 10,100,1000 | | |
38+
| | | | | | speed | 100000 | | |
39+
| | | | | | supported_speeds | 10000,25000,40000,100000 | | |
40+
| | | | | | link_training_status | not_trained | | |
41+
| | | | | +----------------------+--------------------------+ | |
42+
| | | +----------------------+-----------------------------------------------------+ |
43+
+-------------+-----------+--------------------------------------------------------------------------------+
4344
'''
4445

4546

@@ -121,7 +122,7 @@ def test_identifier_single(self):
121122
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': []},
122123
'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': []},
123124
'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'}},
124-
'STATE_DB': {'keys': [{'PORT_TABLE|Ethernet0': {'rmt_adv_speeds': '10,100,1000', 'speed': '100000', 'supported_speeds': '10000,25000,40000,100000'}}], 'tables_not_found': []}}}
125+
'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': []}}}
125126

126127
assert result.exit_code == 0, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
127128
# 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
@@ -138,7 +139,7 @@ def test_identifier_multiple(self):
138139
{"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": []},
139140
"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": []},
140141
"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"}},
141-
"STATE_DB": {"keys": [{"PORT_TABLE|Ethernet0": {"rmt_adv_speeds": "10,100,1000", "speed": "100000", "supported_speeds": "10000,25000,40000,100000"}}], "tables_not_found": []}},
142+
"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": []}},
142143
"Ethernet4":
143144
{"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": []},
144145
"APPL_DB": {"keys": [], "tables_not_found": ["PORT_TABLE"]},
@@ -167,7 +168,7 @@ def test_option_db_filtering(self):
167168
result = runner.invoke(dump.state, ["port", "Ethernet0", "--db", "ASIC_DB", "--db", "STATE_DB"])
168169
print(result.output)
169170
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"}},
170-
"STATE_DB": {"keys": [{"PORT_TABLE|Ethernet0": {"rmt_adv_speeds": "10,100,1000", "speed": "100000", "supported_speeds": "10000,25000,40000,100000"}}], "tables_not_found": []}}}
171+
"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": []}}}
171172
assert result.exit_code == 0, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
172173
ddiff = compare_json_output(expected, result.output)
173174
assert not ddiff, ddiff

0 commit comments

Comments
 (0)