diff --git a/config/main.py b/config/main.py index 4444eac7c0..caec3a78b1 100644 --- a/config/main.py +++ b/config/main.py @@ -3638,14 +3638,18 @@ def mtu(ctx, interface_name, interface_mtu, verbose): """Set interface mtu""" # 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!") - portchannel_member_table = config_db.get_table('PORTCHANNEL_MEMBER') - if interface_is_in_portchannel(portchannel_member_table, interface_name): - ctx.fail("'interface_name' is in portchannel!") + if not interface_name.startswith("PortChannel"): + # The checks are only for Ethernet interfaces and their aliases + if clicommon.get_interface_naming_mode() == "alias": + name_from_alias = interface_alias_to_name(config_db, interface_name) + if interface_name is None or name_from_alias == interface_name: + ctx.fail(f"Failed to convert alias {interface_name} to interface name. Result: {str(interface_name)}") + interface_name = name_from_alias + + portchannel_member_table = config_db.get_table('PORTCHANNEL_MEMBER') + if interface_is_in_portchannel(portchannel_member_table, interface_name): + ctx.fail(f"{interface_name} is in portchannel!") if ctx.obj['namespace'] is DEFAULT_NAMESPACE: command = "portconfig -p {} -m {}".format(interface_name, interface_mtu) diff --git a/scripts/portconfig b/scripts/portconfig index 17b3255ef5..a57abcecac 100755 --- a/scripts/portconfig +++ b/scripts/portconfig @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/python3 """ portconfig is the utility to show and change ECN configuration @@ -25,6 +25,7 @@ optional arguments: import os import sys import argparse +from sonic_py_common import interface # mock the redis for unit test purposes # try: @@ -34,6 +35,9 @@ try: sys.path.insert(0, modules_path) sys.path.insert(0, test_path) import mock_tables.dbconnector + if os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] == "multi_asic": + import mock_tables.mock_multi_asic + mock_tables.dbconnector.load_namespace_config() except KeyError: pass @@ -67,7 +71,7 @@ VALID_INTERFACE_TYPE_SET = set(['CR','CR2','CR4','SR','SR2','SR4', class portconfig(object): """ - Process aclstat + Process portconfig """ def __init__(self, verbose, port, namespace): self.verbose = verbose @@ -100,13 +104,13 @@ class portconfig(object): else: raise Exception("Invalid port %s" % (port)) - def list_params(self, port): + def list_params(self, table_name, port): # chack whether table for this port exists - port_tables = self.db.get_table(PORT_TABLE_NAME) + port_tables = self.db.get_table(table_name) if port in port_tables: print(port_tables[port]) - def set_speed(self, port, speed): + def set_speed(self, table_name, port, speed): if self.verbose: print("Setting speed %s on port %s" % (speed, port)) supported_speeds_str = self.get_supported_speeds(port) @@ -115,25 +119,25 @@ class portconfig(object): print('Invalid speed specified: {}'.format(speed)) print('Valid speeds:{}'.format(supported_speeds_str)) exit(1) - self.db.mod_entry(PORT_TABLE_NAME, port, {PORT_SPEED_CONFIG_FIELD_NAME: speed}) + self.db.mod_entry(table_name, port, {PORT_SPEED_CONFIG_FIELD_NAME: speed}) - def set_fec(self, port, fec): + def set_fec(self, table_name, port, fec): if self.verbose: print("Setting fec %s on port %s" % (fec, port)) - self.db.mod_entry(PORT_TABLE_NAME, port, {PORT_FEC_CONFIG_FIELD_NAME: fec}) + self.db.mod_entry(table_name, port, {PORT_FEC_CONFIG_FIELD_NAME: fec}) - def set_mtu(self, port, mtu): + def set_mtu(self, table_name, port, mtu): if self.verbose: print("Setting mtu %s on port %s" % (mtu, port)) - self.db.mod_entry(PORT_TABLE_NAME, port, {PORT_MTU_CONFIG_FIELD_NAME: mtu}) + self.db.mod_entry(table_name, port, {PORT_MTU_CONFIG_FIELD_NAME: mtu}) - def set_autoneg(self, port, mode): + def set_autoneg(self, table_name, port, mode): if self.verbose: print("Setting autoneg %s on port %s" % (mode, port)) mode = 'on' if mode == 'enabled' else 'off' - self.db.mod_entry(PORT_TABLE_NAME, port, {PORT_AUTONEG_CONFIG_FIELD_NAME: mode}) + self.db.mod_entry(table_name, port, {PORT_AUTONEG_CONFIG_FIELD_NAME: mode}) - def set_adv_speeds(self, port, adv_speeds): + def set_adv_speeds(self, table_name, port, adv_speeds): if self.verbose: print("Setting adv_speeds %s on port %s" % (adv_speeds, port)) @@ -152,18 +156,18 @@ class portconfig(object): print('Valid speeds:{}'.format(supported_speeds_str)) exit(1) - self.db.mod_entry(PORT_TABLE_NAME, port, {PORT_ADV_SPEEDS_CONFIG_FIELD_NAME: adv_speeds}) + self.db.mod_entry(table_name, port, {PORT_ADV_SPEEDS_CONFIG_FIELD_NAME: adv_speeds}) - def set_interface_type(self, port, interface_type): + def set_interface_type(self, table_name, port, interface_type): if self.verbose: print("Setting interface_type %s on port %s" % (interface_type, port)) if interface_type not in VALID_INTERFACE_TYPE_SET: print("Invalid interface type specified: {}".format(interface_type)) print("Valid interface types:{}".format(','.join(VALID_INTERFACE_TYPE_SET))) exit(1) - self.db.mod_entry(PORT_TABLE_NAME, port, {PORT_INTERFACE_TYPE_CONFIG_FIELD_NAME: interface_type}) + self.db.mod_entry(table_name, port, {PORT_INTERFACE_TYPE_CONFIG_FIELD_NAME: interface_type}) - def set_adv_interface_types(self, port, adv_interface_types): + def set_adv_interface_types(self, table_name, port, adv_interface_types): if self.verbose: print("Setting adv_interface_types %s on port %s" % (adv_interface_types, port)) @@ -178,7 +182,8 @@ class portconfig(object): print("Invalid interface type specified: {}".format(','.join(invalid_interface_types))) print("Valid interface types:{}".format(','.join(VALID_INTERFACE_TYPE_SET))) exit(1) - self.db.mod_entry(PORT_TABLE_NAME, port, {PORT_ADV_INTERFACE_TYPES_CONFIG_FIELD_NAME: adv_interface_types}) + + self.db.mod_entry(table_name, port, {PORT_ADV_INTERFACE_TYPES_CONFIG_FIELD_NAME: adv_interface_types}) def get_supported_speeds(self, port): if not self.namespace: @@ -188,7 +193,7 @@ class portconfig(object): state_db.connect(state_db.STATE_DB) return state_db.get(state_db.STATE_DB, '{}|{}'.format(PORT_STATE_TABLE_NAME, port), PORT_STATE_SUPPORTED_SPEEDS) - def set_tpid(self, port, tpid): + def set_tpid(self, table_name, port, tpid): if self.verbose: print("Setting tpid %s on port %s" % (tpid, port)) @@ -223,7 +228,7 @@ class portconfig(object): raise Exception("%s is already member of %s. Set TPID NOT allowed." % (port, self.parent)) else: if tpid_port_capable: - self.db.mod_entry(PORT_TABLE_NAME, port, {TPID_CONFIG_FIELD_NAME: tpid}) + self.db.mod_entry(table_name, port, {TPID_CONFIG_FIELD_NAME: tpid}) else: raise Exception("HW is not capable to support Port TPID config.") else: @@ -236,15 +241,15 @@ def main(): parser = argparse.ArgumentParser(description='Set SONiC port parameters', formatter_class=argparse.RawTextHelpFormatter) parser.add_argument('-p', '--port', type=str, help='port name (e.g. Ethernet0)', required=True, default=None) - parser.add_argument('-l', '--list', action='store_true', help='list port parametars', default=False) + parser.add_argument('-l', '--list', action='store_true', help='list port parameters', default=False) parser.add_argument('-s', '--speed', type=int, help='port speed value in Mbit', default=None) parser.add_argument('-f', '--fec', type=str, help='port fec mode value in (none, rs, fc)', default=None) parser.add_argument('-m', '--mtu', type=int, help='port mtu value in bytes', default=None) parser.add_argument('-tp', '--tpid', type=str, help='port TPID value in hex (e.g. 0x8100)', default=None) parser.add_argument('-v', '--version', action='version', version='%(prog)s 1.0') parser.add_argument('-vv', '--verbose', action='store_true', help='Verbose output', default=False) - parser.add_argument('-n', '--namespace', metavar='namespace details', type = str, required = False, - help = 'The asic namespace whose DB instance we need to connect', default=None) + parser.add_argument('-n', '--namespace', metavar='namespace details', type=str, required=False, + help='The asic namespace whose DB instance we need to connect', default=None) parser.add_argument('-an', '--autoneg', type = str, required = False, help = 'port auto negotiation mode', default=None) parser.add_argument('-S', '--adv-speeds', type = str, required = False, @@ -258,26 +263,30 @@ def main(): # Load database config files load_db_config() try: + port_table_name = interface.get_port_table_name(args.port) + if not port_table_name: + raise Exception("Invalid port type specified") + port = portconfig(args.verbose, args.port, args.namespace) if args.list: - port.list_params(args.port) + port.list_params(port_table_name, 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: if args.speed: - port.set_speed(args.port, args.speed) + port.set_speed(port_table_name, args.port, args.speed) if args.fec: - port.set_fec(args.port, args.fec) + port.set_fec(port_table_name, args.port, args.fec) if args.mtu: - port.set_mtu(args.port, args.mtu) + port.set_mtu(port_table_name, args.port, args.mtu) if args.autoneg: - port.set_autoneg(args.port, args.autoneg) + port.set_autoneg(port_table_name, args.port, args.autoneg) if args.adv_speeds: - port.set_adv_speeds(args.port, args.adv_speeds) + port.set_adv_speeds(port_table_name, args.port, args.adv_speeds) if args.interface_type: - port.set_interface_type(args.port, args.interface_type) + port.set_interface_type(port_table_name, args.port, args.interface_type) if args.adv_interface_types: - port.set_adv_interface_types(args.port, args.adv_interface_types) + port.set_adv_interface_types(port_table_name, args.port, args.adv_interface_types) if args.tpid: - port.set_tpid(args.port, args.tpid) + port.set_tpid(port_table_name, args.port, args.tpid) else: parser.print_help() sys.exit(1) diff --git a/tests/portconfig_test.py b/tests/portconfig_test.py new file mode 100644 index 0000000000..cdae5a5b48 --- /dev/null +++ b/tests/portconfig_test.py @@ -0,0 +1,236 @@ +import os +import sys +from click.testing import CliRunner +from unittest import TestCase +import subprocess + +from utilities_common.db import Db +import config.main as config +from .utils import get_result_and_return_code + +root_path = os.path.dirname(os.path.abspath(__file__)) +modules_path = os.path.dirname(root_path) +scripts_path = os.path.join(modules_path, "scripts") + +config_intf_mtu_eth0_255_verbose_output = """\ +Running command: portconfig -p Ethernet0 -m 255 -vv +Setting mtu 255 on port Ethernet0 +""" + +config_intf_mtu_eth32_portchannel_member_output = """\ +Usage: mtu [OPTIONS] +Try "mtu --help" for help. + +Error: Ethernet32 is in portchannel! +""" + +config_intf_mtu_etp666_bad_alias_output = """\ +Usage: mtu [OPTIONS] +Try "mtu --help" for help. + +Error: Failed to convert alias etp666 to interface name. Result: etp666 +""" + +config_intf_mtu_invalid_port_type_command = \ +"Invalid port type specified" + +config_intf_mtu_multiasic_wrong_namespace_portconfig_output = \ +"asic666 is not a valid namespace name in configuration file" + +config_intf_multiasic_wrong_namespace_output = """\ +Usage: interface [OPTIONS] COMMAND [ARGS]... +Try "interface --help" for help. + +Error: Invalid value for "-n" / "--namespace": invalid choice: asic666. (choose from asic0, asic1) +""" + +config_intf_output = """\ +Usage: interface [OPTIONS] COMMAND [ARGS]... +Try "interface --help" for help. + +Error: Missing command. +""" + +class TestPortconfig(TestCase): + @classmethod + def setup_class(cls): + print("SETUP") + os.environ["PATH"] += os.pathsep + scripts_path + os.environ["UTILITIES_UNIT_TESTING"] = "1" + + def setUp(self): + self.runner = CliRunner() + + def test_config_intf(self): + runner = CliRunner() + + result = runner.invoke(config.config.commands["interface"], + ["--namespace", "asic0"]) + print(result.exit_code, result.output) + assert result.exit_code == 2 + assert config_intf_output == result.output + + def test_config_intf_multiasic(self): + runner = CliRunner() + + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "multi_asic" + result = runner.invoke(config.config.commands["interface"], + ["--namespace", "asic0"]) + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "" + print(result.exit_code, result.output) + assert result.exit_code == 2 + assert config_intf_output == result.output + + def test_config_intf_multiasic_bad_namespace(self): + runner = CliRunner() + + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "multi_asic" + result = runner.invoke(config.config.commands["interface"], + ["--namespace", "asic666"]) + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "" + print(result.exit_code, result.output) + assert result.exit_code == 2 + assert config_intf_multiasic_wrong_namespace_output == result.output + + def test_config_intf_mtu_invalid_port_type(self): + db = Db() + runner = CliRunner() + obj = {'config_db': db.cfgdb, 'namespace': ''} + + result = runner.invoke(config.config.commands["interface"].commands["mtu"], + ["Something0", "255"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 1 + + return_code, result = get_result_and_return_code('portconfig -p Something0 -m 255') + print("return_code: {}".format(return_code)) + print("result = {}".format(result)) + assert return_code == 1 + assert result == config_intf_mtu_invalid_port_type_command + + def test_config_intf_mtu_ethernet(self): + db = Db() + runner = CliRunner() + obj = {'config_db': db.cfgdb, 'namespace': ''} + + result = runner.invoke(config.config.commands["interface"].commands["mtu"], + ["Ethernet0", "255"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + + return_code, result = get_result_and_return_code('portconfig -p Ethernet0 -m 255') + print("return_code: {}".format(return_code)) + assert return_code == 0 + + def test_config_intf_mtu_portchannel(self): + db = Db() + runner = CliRunner() + obj = {'config_db': db.cfgdb, 'namespace': ''} + + result = runner.invoke(config.config.commands["interface"].commands["mtu"], + ["PortChannel0001", "255"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + + return_code, result = get_result_and_return_code('portconfig -p PortChannel0001 -m 255') + print("return_code: {}".format(return_code)) + assert return_code == 0 + + def test_config_intf_mtu_ethernet_portchannel_member(self): + db = Db() + runner = CliRunner() + obj = {'config_db': db.cfgdb, 'namespace': ''} + + result = runner.invoke(config.config.commands["interface"].commands["mtu"], + ["Ethernet32", "255"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 2 + assert config_intf_mtu_eth32_portchannel_member_output == result.output + + def test_config_intf_mtu_ethernet_alias(self): + db = Db() + runner = CliRunner() + obj = {'config_db': db.cfgdb, 'namespace': ''} + + os.environ['SONIC_CLI_IFACE_MODE'] = "alias" + result = runner.invoke(config.config.commands["interface"].commands["mtu"], + ["etp1", "255"], obj=obj) + os.environ['SONIC_CLI_IFACE_MODE'] = "default" + print(result.exit_code, result.output) + assert result.exit_code == 0 + + def test_config_intf_mtu_ethernet_alias_bad(self): + db = Db() + runner = CliRunner() + obj = {'config_db': db.cfgdb, 'namespace': ''} + + os.environ['SONIC_CLI_IFACE_MODE'] = "alias" + result = runner.invoke(config.config.commands["interface"].commands["mtu"], + ["etp666", "255"], obj=obj) + os.environ['SONIC_CLI_IFACE_MODE'] = "default" + print(result.exit_code, result.output) + assert result.exit_code == 2 + assert config_intf_mtu_etp666_bad_alias_output == result.output + + def test_config_intf_mtu_ethernet_alias_portchannel_member(self): + db = Db() + runner = CliRunner() + obj = {'config_db': db.cfgdb, 'namespace': ''} + + os.environ['SONIC_CLI_IFACE_MODE'] = "alias" + result = runner.invoke(config.config.commands["interface"].commands["mtu"], + ["etp9", "255"], obj=obj) + os.environ['SONIC_CLI_IFACE_MODE'] = "default" + print(result.exit_code, result.output) + assert result.exit_code == 2 + assert config_intf_mtu_eth32_portchannel_member_output == result.output + + def test_config_intf_mtu_ethernet_verbose(self): + db = Db() + runner = CliRunner() + obj = {'config_db': db.cfgdb, 'namespace': ''} + + result = runner.invoke(config.config.commands["interface"].commands["mtu"], + ["Ethernet0", "255", "-v"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert result.output == config_intf_mtu_eth0_255_verbose_output + + def test_config_intf_mtu_ethernet_multiasic(self): + db = Db() + runner = CliRunner() + obj = {'config_db': db.cfgdb, 'namespace': 'asic0'} + + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "multi_asic" + result = runner.invoke(config.config.commands["interface"].commands["mtu"], + ["Ethernet0", "255"], obj=obj) + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "" + print(result.exit_code, result.output) + assert result.exit_code == 0 + + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "multi_asic" + return_code, result = get_result_and_return_code('portconfig -p Ethernet0 -n asic0 -m 255') + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "" + print("return_code: {}".format(return_code)) + assert return_code == 0 + + def test_config_intf_mtu_ethernet_multiasic_bad_namespace(self): + db = Db() + runner = CliRunner() + obj = {'config_db': db.cfgdb, 'namespace': 'asic666'} + + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "multi_asic" + return_code, result = get_result_and_return_code('portconfig -p Ethernet0 -n asic666 -m 255') + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "" + print("return_code: {}".format(return_code)) + print("result = {}".format(result)) + assert return_code == 1 + assert config_intf_mtu_multiasic_wrong_namespace_portconfig_output == result + + @classmethod + def teardown_class(cls): + print("TEARDOWN") + os.environ["PATH"] = os.pathsep.join(os.environ["PATH"].split(os.pathsep)[:-1]) + os.environ["UTILITIES_UNIT_TESTING"] = "0" + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "" + os.environ['SONIC_CLI_IFACE_MODE'] = "default"