From e36950ca5161d392356a01caffbdaacf3c7c3fb6 Mon Sep 17 00:00:00 2001 From: sabakram Date: Mon, 1 Jan 2024 12:10:54 +0500 Subject: [PATCH 01/19] Fix for switchport mode PR --- config/main.py | 33 + config/switchport.py | 137 ++++ config/vlan.py | 302 ++++++--- doc/Command-Reference.md | 153 +++++ scripts/db_migrator.py | 33 + show/interfaces/__init__.py | 67 ++ .../config_db/port-an-expected.json | 3 + .../config_db/portchannel-expected.json | 5 + .../config_db/switchport-expected.json | 144 ++++ .../config_db/switchport-input.json | 138 ++++ tests/db_migrator_test.py | 28 + tests/interfaces_test.py | 100 +++ tests/ipv6_link_local_test.py | 2 +- tests/mock_tables/asic0/config_db.json | 1 + tests/mock_tables/config_db.json | 32 + tests/vlan_test.py | 620 +++++++++++++++++- utilities_common/cli.py | 153 +++++ 17 files changed, 1841 insertions(+), 110 deletions(-) create mode 100644 config/switchport.py create mode 100644 tests/db_migrator_input/config_db/switchport-expected.json create mode 100644 tests/db_migrator_input/config_db/switchport-input.json diff --git a/config/main.py b/config/main.py index 65b669be7f..a618465d53 100644 --- a/config/main.py +++ b/config/main.py @@ -57,6 +57,7 @@ from .config_mgmt import ConfigMgmtDPB, ConfigMgmt from . import mclag from . import syslog +from . import switchport from . import dns # mock masic APIs for unit test @@ -105,6 +106,7 @@ PORT_SPEED = "speed" PORT_TPID = "tpid" DEFAULT_TPID = "0x8100" +PORT_MODE= "switchport_mode" asic_type = None @@ -1211,6 +1213,9 @@ def config(ctx): # DNS module config.add_command(dns.dns) +# Switchport module +config.add_command(switchport.switchport) + @config.command() @click.option('-y', '--yes', is_flag=True, callback=_abort_if_false, expose_value=False, prompt='Existing files will be overwritten, continue?') @@ -4544,7 +4549,35 @@ def add(ctx, interface_name, ip_addr, gw): if interface_is_in_portchannel(portchannel_member_table, interface_name): ctx.fail("{} is configured as a member of portchannel." .format(interface_name)) + + + # Add a validation to check this interface is in routed mode before + # assigning an IP address to it + + sub_intf = False + + if clicommon.is_valid_port(config_db, interface_name): + is_port = True + elif clicommon.is_valid_portchannel(config_db, interface_name): + is_port = False + else: + sub_intf = True + + if not sub_intf: + interface_mode = "routed" + if is_port: + interface_data = config_db.get_entry('PORT',interface_name) + elif not is_port: + interface_data = config_db.get_entry('PORTCHANNEL',interface_name) + if "mode" in interface_data: + interface_mode = interface_data["mode"] + + if interface_mode != "routed": + ctx.fail("Interface {} is not in routed mode!".format(interface_name)) + return + + try: ip_address = ipaddress.ip_interface(ip_addr) except ValueError as err: diff --git a/config/switchport.py b/config/switchport.py new file mode 100644 index 0000000000..a714f9427f --- /dev/null +++ b/config/switchport.py @@ -0,0 +1,137 @@ +import click +from .utils import log +import utilities_common.cli as clicommon + +# +# 'switchport' mode ('config switchport ...') +# + + +@click.group(cls=clicommon.AbbreviationGroup, name='switchport') +def switchport(): + """Switchport mode configuration tasks""" + pass + + +@switchport.command("mode") +@click.argument("type", metavar="", required=True, type=click.Choice(["access", "trunk", "routed"])) +@click.argument("port", metavar="port", required=True) +@clicommon.pass_db +def switchport_mode(db, type, port): + """switchport mode help commands.Mode_type can be access or trunk or routed""" + + ctx = click.get_current_context() + + log.log_info("'switchport mode {} {}' executing...".format(type, port)) + mode_exists_status = True + + # checking if port name with alias exists + if clicommon.get_interface_naming_mode() == "alias": + alias = port + iface_alias_converter = clicommon.InterfaceAliasConverter(db) + port = iface_alias_converter.alias_to_name(port) + if port is None: + ctx.fail("cannot find port name for alias {}".format(alias)) + + if clicommon.is_port_mirror_dst_port(db.cfgdb, port): + ctx.fail("{} is configured as mirror destination port".format(port)) + + + if clicommon.is_valid_port(db.cfgdb, port): + is_port = True + elif clicommon.is_valid_portchannel(db.cfgdb, port): + is_port = False + else: + ctx.fail("{} does not exist".format(port)) + + portchannel_member_table = db.cfgdb.get_table('PORTCHANNEL_MEMBER') + + if (is_port and clicommon.interface_is_in_portchannel(portchannel_member_table, port)): + ctx.fail("{} is part of portchannel!".format(port)) + + if is_port: + port_data = db.cfgdb.get_entry('PORT',port) + else: + port_data = db.cfgdb.get_entry('PORTCHANNEL',port) + + # mode type is either access or trunk + if type != "routed": + + if "mode" in port_data: + existing_mode = port_data["mode"] + else: + existing_mode = "routed" + mode_exists_status = False + if (is_port and clicommon.is_port_router_interface(db.cfgdb, port)) or \ + (not is_port and clicommon.is_pc_router_interface(db.cfgdb, port)): + ctx.fail("Remove IP from {} to change mode!".format(port)) + + if existing_mode == "routed": + if mode_exists_status: + # if the port in an interface + if is_port: + db.cfgdb.mod_entry("PORT", port, {"mode": "{}".format(type)}) + # if not port then is a port channel + elif not is_port: + db.cfgdb.mod_entry("PORTCHANNEL", port, {"mode": "{}".format(type)}) + + if not mode_exists_status: + port_data["mode"] = type + if is_port: + db.cfgdb.set_entry("PORT", port, port_data) + # if not port then is a port channel + elif not is_port: + db.cfgdb.set_entry("PORTCHANNEL", port, port_data) + + if existing_mode == type: + ctx.fail("{} is already in the {} mode".format(port,type)) + else: + if existing_mode == "access" and type == "trunk": + pass + if existing_mode == "trunk" and type == "access": + if clicommon.interface_is_tagged_member(db.cfgdb,port): + ctx.fail("{} is in {} mode and have tagged member(s).\nRemove tagged member(s) from {} to switch to {} mode".format(port,existing_mode,port,type)) + if is_port: + db.cfgdb.mod_entry("PORT", port, {"mode": "{}".format(type)}) + # if not port then is a port channel + elif not is_port: + db.cfgdb.mod_entry("PORTCHANNEL", port, {"mode": "{}".format(type)}) + + click.echo("{} switched from {} to {} mode".format(port, existing_mode, type)) + + # if mode type is routed + else: + + if clicommon.interface_is_tagged_member(db.cfgdb,port): + ctx.fail("{} has tagged member(s). \nRemove them to change mode to {}".format(port,type)) + + if clicommon.interface_is_untagged_member(db.cfgdb,port): + ctx.fail("{} has untagged member. \nRemove it to change mode to {}".format(port,type)) + + if "mode" in port_data: + existing_mode = port_data["mode"] + else: + existing_mode = "routed" + mode_exists_status = False + + if not mode_exists_status: + port_data["mode"] = type + if is_port: + db.cfgdb.set_entry("PORT", port, port_data) + + # if not port then is a port channel + elif not is_port: + db.cfgdb.set_entry("PORTCHANNEL", port, port_data) + pass + + elif mode_exists_status and existing_mode == type: + ctx.fail("{} is already in {} mode".format(port,type)) + + else: + if is_port: + db.cfgdb.mod_entry("PORT", port, {"mode": "{}".format(type)}) + # if not port then is a port channel + elif not is_port: + db.cfgdb.mod_entry("PORTCHANNEL", port, {"mode": "{}".format(type)}) + + click.echo("{} switched from {} to {} mode".format(port,existing_mode,type)) diff --git a/config/vlan.py b/config/vlan.py index 7ace1d6d5f..df98ad9324 100644 --- a/config/vlan.py +++ b/config/vlan.py @@ -33,25 +33,49 @@ def is_dhcp_relay_running(): @vlan.command('add') @click.argument('vid', metavar='', required=True, type=int) +@click.option('-m', '--multiple', is_flag=True, help="Add Multiple Vlan(s) in Range or in Comma separated list") @clicommon.pass_db -def add_vlan(db, vid): - """Add VLAN""" +def add_vlan(db, vid, multiple): + """Add VLAN""" ctx = click.get_current_context() - vlan = 'Vlan{}'.format(vid) config_db = ValidatedConfigDBConnector(db.cfgdb) + + vid_list = [] + # parser will parse the vid input if there are syntax errors it will throw error + if multiple: + vid_list = clicommon.multiple_vlan_parser(ctx, vid) + else: + if not vid.isdigit(): + ctx.fail("{} is not integer".format(vid)) + vid_list.append(int(vid)) + if ADHOC_VALIDATION: - if not clicommon.is_vlanid_in_range(vid): - ctx.fail("Invalid VLAN ID {} (1-4094)".format(vid)) - if vid == 1: - ctx.fail("{} is default VLAN".format(vlan)) # TODO: MISSING CONSTRAINT IN YANG MODEL + # loop will execute till an exception occurs + for vid in vid_list: + + vlan = 'Vlan{}'.format(vid) + + # default vlan checker + if vid == 1: + # TODO: MISSING CONSTRAINT IN YANG MODEL + ctx.fail("{} is default VLAN.".format(vlan)) + + log.log_info("'vlan add {}' executing...".format(vid)) + + if not clicommon.is_vlanid_in_range(vid): + ctx.fail("Invalid VLAN ID {} (2-4094)".format(vid)) + + # TODO: MISSING CONSTRAINT IN YANG MODEL + if clicommon.check_if_vlanid_exist(db.cfgdb, vlan): + log.log_info("{} already exists".format(vlan)) + ctx.fail("{} already exists, Aborting!!!".format(vlan)) + + if clicommon.check_if_vlanid_exist(db.cfgdb, vlan, "DHCP_RELAY"): + ctx.fail("DHCPv6 relay config for {} already exists".format(vlan)) - if clicommon.check_if_vlanid_exist(db.cfgdb, vlan): # TODO: MISSING CONSTRAINT IN YANG MODEL - ctx.fail("{} already exists".format(vlan)) - if clicommon.check_if_vlanid_exist(db.cfgdb, vlan, "DHCP_RELAY"): - ctx.fail("DHCPv6 relay config for {} already exists".format(vlan)) # set dhcpv4_relay table set_dhcp_relay_table('VLAN', config_db, vlan, {'vlanid': str(vid)}) @@ -75,6 +99,7 @@ def delete_db_entry(entry_name, db_connector, db_name): @vlan.command('del') @click.argument('vid', metavar='', required=True, type=int) +@click.option('-m', '--multiple', is_flag=True, help="Add Multiple Vlan(s) in Range or in Comma separated list") @click.option('--no_restart_dhcp_relay', is_flag=True, type=click.BOOL, required=False, default=False, help="If no_restart_dhcp_relay is True, do not restart dhcp_relay while del vlan and \ require dhcpv6 relay of this is empty") @@ -85,35 +110,47 @@ def del_vlan(db, vid, no_restart_dhcp_relay): log.log_info("'vlan del {}' executing...".format(vid)) ctx = click.get_current_context() - vlan = 'Vlan{}'.format(vid) + + vid_list = [] + # parser will parse the vid input if there are syntax errors it will throw error + if multiple: + vid_list = clicommon.multiple_vlan_parser(ctx, vid) + else: + if not vid.isdigit(): + ctx.fail("{} is not integer".format(vid)) + vid_list.append(int(vid)) + if no_restart_dhcp_relay: if is_dhcpv6_relay_config_exist(db, vlan): ctx.fail("Can't delete {} because related DHCPv6 Relay config is exist".format(vlan)) config_db = ValidatedConfigDBConnector(db.cfgdb) if ADHOC_VALIDATION: - if not clicommon.is_vlanid_in_range(vid): - ctx.fail("Invalid VLAN ID {} (1-4094)".format(vid)) - - if clicommon.check_if_vlanid_exist(db.cfgdb, vlan) == False: - ctx.fail("{} does not exist".format(vlan)) - - intf_table = db.cfgdb.get_table('VLAN_INTERFACE') - for intf_key in intf_table: - if ((type(intf_key) is str and intf_key == 'Vlan{}'.format(vid)) or # TODO: MISSING CONSTRAINT IN YANG MODEL - (type(intf_key) is tuple and intf_key[0] == 'Vlan{}'.format(vid))): - ctx.fail("{} can not be removed. First remove IP addresses assigned to this VLAN".format(vlan)) - - keys = [ (k, v) for k, v in db.cfgdb.get_table('VLAN_MEMBER') if k == 'Vlan{}'.format(vid) ] - - if keys: # TODO: MISSING CONSTRAINT IN YANG MODEL - ctx.fail("VLAN ID {} can not be removed. First remove all members assigned to this VLAN.".format(vid)) - - vxlan_table = db.cfgdb.get_table('VXLAN_TUNNEL_MAP') - for vxmap_key, vxmap_data in vxlan_table.items(): - if vxmap_data['vlan'] == 'Vlan{}'.format(vid): - ctx.fail("vlan: {} can not be removed. First remove vxlan mapping '{}' assigned to VLAN".format(vid, '|'.join(vxmap_key)) ) - + for vid in vid_list: + log.log_info("'vlan del {}' executing...".format(vid)) + if not clicommon.is_vlanid_in_range(vid): + ctx.fail("Invalid VLAN ID {} (2-4094)".format(vid)) + vlan = 'Vlan{}'.format(vid) + if clicommon.check_if_vlanid_exist(db.cfgdb, vlan) == False: + log.log_info("{} does not exist".format(vlan)) + ctx.fail("{} does not exist, Aborting!!!".format(vlan)) + + intf_table = db.cfgdb.get_table('VLAN_INTERFACE') + for intf_key in intf_table: + if ((type(intf_key) is str and intf_key == 'Vlan{}'.format(vid)) or # TODO: MISSING CONSTRAINT IN YANG MODEL + (type(intf_key) is tuple and intf_key[0] == 'Vlan{}'.format(vid))): + ctx.fail("{} can not be removed. First remove IP addresses assigned to this VLAN".format(vlan)) + + keys = [(k, v) for k, v in db.cfgdb.get_table('VLAN_MEMBER') if k == 'Vlan{}'.format(vid)] + + if keys: # TODO: MISSING CONSTRAINT IN YANG MODEL + ctx.fail("VLAN ID {} can not be removed. First remove all members assigned to this VLAN.".format(vid)) + + vxlan_table = db.cfgdb.get_table('VXLAN_TUNNEL_MAP') + for vxmap_key, vxmap_data in vxlan_table.items(): + if vxmap_data['vlan'] == 'Vlan{}'.format(vid): + ctx.fail("vlan: {} can not be removed. First remove vxlan mapping '{}' assigned to VLAN".format(vid, '|'.join(vxmap_key))) + # set dhcpv4_relay table set_dhcp_relay_table('VLAN', config_db, vlan, None) @@ -123,6 +160,7 @@ def del_vlan(db, vid, no_restart_dhcp_relay): # We need to restart dhcp_relay service after dhcpv6_relay config change if is_dhcp_relay_running(): dhcp_relay_util.handle_restart_dhcp_relay_service() + delete_db_entry("DHCPv6_COUNTER_TABLE|{}".format(vlan), db.db, db.db.STATE_DB) delete_db_entry("DHCP_COUNTER_TABLE|{}".format(vlan), db.db, db.db.STATE_DB) @@ -191,95 +229,153 @@ def vlan_member(): @vlan_member.command('add') @click.argument('vid', metavar='', required=True, type=int) @click.argument('port', metavar='port', required=True) -@click.option('-u', '--untagged', is_flag=True) +@click.option('-u', '--untagged', is_flag=True, help="Untagged status") +@click.option('-m', '--multiple', is_flag=def add_vlan_member(db, vid, port, untagged, multiple, except_flag):True, help="Add Multiple Vlan(s) in Range or in Comma separated list") +@click.option('-e', '--except_flag', is_flag=True, help="Skips the given vlans and adds all other existing vlans") @clicommon.pass_db -def add_vlan_member(db, vid, port, untagged): +def add_vlan_member(db, vid, port, untagged, multiple, except_flag): """Add VLAN member""" ctx = click.get_current_context() log.log_info("'vlan member add {} {}' executing...".format(vid, port)) - vlan = 'Vlan{}'.format(vid) + # parser will parse the vid input if there are syntax errors it will throw error + + vid_list = clicommon.vlan_member_input_parser(ctx, "add", db, except_flag, multiple, vid, port) + + # multiple vlan command cannot be used to add multiple untagged vlan members + if untagged and (multiple or except_flag or vid == "all"): + ctx.fail("{} cannot have more than one untagged Vlan.".format(port)) config_db = ValidatedConfigDBConnector(db.cfgdb) + if ADHOC_VALIDATION: - if not clicommon.is_vlanid_in_range(vid): - ctx.fail("Invalid VLAN ID {} (1-4094)".format(vid)) - - if clicommon.check_if_vlanid_exist(db.cfgdb, vlan) == False: - ctx.fail("{} does not exist".format(vlan)) - - if clicommon.get_interface_naming_mode() == "alias": # TODO: MISSING CONSTRAINT IN YANG MODEL - alias = port - iface_alias_converter = clicommon.InterfaceAliasConverter(db) - port = iface_alias_converter.alias_to_name(alias) - if port is None: - ctx.fail("cannot find port name for alias {}".format(alias)) - - if clicommon.is_port_mirror_dst_port(db.cfgdb, port): # TODO: MISSING CONSTRAINT IN YANG MODEL - ctx.fail("{} is configured as mirror destination port".format(port)) - - if clicommon.is_port_vlan_member(db.cfgdb, port, vlan): # TODO: MISSING CONSTRAINT IN YANG MODEL - ctx.fail("{} is already a member of {}".format(port, vlan)) - - if clicommon.is_valid_port(db.cfgdb, port): - is_port = True - elif clicommon.is_valid_portchannel(db.cfgdb, port): - is_port = False - else: - ctx.fail("{} does not exist".format(port)) - - if (is_port and clicommon.is_port_router_interface(db.cfgdb, port)) or \ - (not is_port and clicommon.is_pc_router_interface(db.cfgdb, port)): # TODO: MISSING CONSTRAINT IN YANG MODEL - ctx.fail("{} is a router interface!".format(port)) + for vid in vid_list: + + # default vlan checker + if vid == 1: + ctx.fail("{} is default VLAN".format(vlan)) + + log.log_info("'vlan member add {} {}' executing...".format(vid, port)) + + if not clicommon.is_vlanid_in_range(vid): + ctx.fail("Invalid VLAN ID {} (2-4094)".format(vid)) + vlan = 'Vlan{}'.format(vid) + + if clicommon.check_if_vlanid_exist(db.cfgdb, vlan) == False: + log.log_info("{} does not exist".format(vlan)) + ctx.fail("{} does not exist".format(vlan)) + + if clicommon.get_interface_naming_mode() == "alias": # TODO: MISSING CONSTRAINT IN YANG MODEL + alias = port + iface_alias_converter = clicommon.InterfaceAliasConverter(db) + port = iface_alias_converter.alias_to_name(alias) + if port is None: + ctx.fail("cannot find port name for alias {}".format(alias)) + + # TODO: MISSING CONSTRAINT IN YANG MODEL + if clicommon.is_port_mirror_dst_port(db.cfgdb, port): + ctx.fail("{} is configured as mirror destination port".format(port)) + + # TODO: MISSING CONSTRAINT IN YANG MODEL + if clicommon.is_port_vlan_member(db.cfgdb, port, vlan): + log.log_info("{} is already a member of {}, Aborting!!!".format(port, vlan)) + ctx.fail("{} is already a member of {}, Aborting!!!".format(port, vlan)) + + + if clicommon.is_valid_port(db.cfgdb, port): + is_port = True + elif clicommon.is_valid_portchannel(db.cfgdb, port): + is_port = False + else: + ctx.fail("{} does not exist".format(port)) + + portchannel_member_table = db.cfgdb.get_table('PORTCHANNEL_MEMBER') + + # TODO: MISSING CONSTRAINT IN YANG MODEL + if (is_port and clicommon.interface_is_in_portchannel(portchannel_member_table, port)): + ctx.fail("{} is part of portchannel!".format(port)) + + # TODO: MISSING CONSTRAINT IN YANG MODEL + if (clicommon.interface_is_untagged_member(db.cfgdb, port) and untagged): + ctx.fail("{} is already untagged member!".format(port)) + + # checking mode status of port if its access, trunk or routed + if is_port: + port_data = config_db.get_entry('PORT',port) + + # if not port then is a port channel + elif not is_port: + port_data = config_db.get_entry('PORTCHANNEL',port) + + if "mode" not in port_data: + ctx.fail("{} is in routed mode!\nUse switchport mode command to change port mode".format(port)) + else: + existing_mode = port_data["mode"] + + if existing_mode == "routed": + ctx.fail("{} is in routed mode!\nUse switchport mode command to change port mode".format(port)) + + mode_type = "access" if untagged else "trunk" + if existing_mode == "access" and mode_type == "trunk": # TODO: MISSING CONSTRAINT IN YANG MODEL + ctx.fail("{} is in access mode! Tagged Members cannot be added".format(port)) + + elif existing_mode == mode_type or (existing_mode == "trunk" and mode_type == "access"): + pass + + # in case of exception in list last added member will be shown to user - portchannel_member_table = db.cfgdb.get_table('PORTCHANNEL_MEMBER') - - if (is_port and clicommon.interface_is_in_portchannel(portchannel_member_table, port)): # TODO: MISSING CONSTRAINT IN YANG MODEL - ctx.fail("{} is part of portchannel!".format(port)) - - if (clicommon.interface_is_untagged_member(db.cfgdb, port) and untagged): # TODO: MISSING CONSTRAINT IN YANG MODEL - ctx.fail("{} is already untagged member!".format(port)) - - try: - config_db.set_entry('VLAN_MEMBER', (vlan, port), {'tagging_mode': "untagged" if untagged else "tagged" }) - except ValueError: - ctx.fail("{} invalid or does not exist, or {} invalid or does not exist".format(vlan, port)) + try: + config_db.set_entry('VLAN_MEMBER', (vlan, port), {'tagging_mode': "untagged" if untagged else "tagged" }) + except ValueError: + ctx.fail("{} invalid or does not exist, or {} invalid or does not exist".format(vlan, port)) @vlan_member.command('del') @click.argument('vid', metavar='', required=True, type=int) @click.argument('port', metavar='', required=True) +@click.option('-m', '--multiple', is_flag=True, help="Add Multiple Vlan(s) in Range or in Comma separated list") +@click.option('-e', '--except_flag', is_flag=True, help="Skips the given vlans and adds all other existing vlans") @clicommon.pass_db -def del_vlan_member(db, vid, port): +def del_vlan_member(db, vid, port, multiple, except_flag): """Delete VLAN member""" ctx = click.get_current_context() - log.log_info("'vlan member del {} {}' executing...".format(vid, port)) - vlan = 'Vlan{}'.format(vid) + # parser will parse the vid input if there are syntax errors it will throw error + + vid_list = clicommon.vlan_member_input_parser(ctx,"del", db, except_flag, multiple, vid, port) + config_db = ValidatedConfigDBConnector(db.cfgdb) if ADHOC_VALIDATION: - if not clicommon.is_vlanid_in_range(vid): - ctx.fail("Invalid VLAN ID {} (1-4094)".format(vid)) - - if clicommon.check_if_vlanid_exist(db.cfgdb, vlan) == False: - ctx.fail("{} does not exist".format(vlan)) - - if clicommon.get_interface_naming_mode() == "alias": # TODO: MISSING CONSTRAINT IN YANG MODEL - alias = port - iface_alias_converter = clicommon.InterfaceAliasConverter(db) - port = iface_alias_converter.alias_to_name(alias) - if port is None: - ctx.fail("cannot find port name for alias {}".format(alias)) - - if not clicommon.is_port_vlan_member(db.cfgdb, port, vlan): # TODO: MISSING CONSTRAINT IN YANG MODEL - ctx.fail("{} is not a member of {}".format(port, vlan)) - - try: - config_db.set_entry('VLAN_MEMBER', (vlan, port), None) - delete_db_entry("DHCPv6_COUNTER_TABLE|{}".format(port), db.db, db.db.STATE_DB) - delete_db_entry("DHCP_COUNTER_TABLE|{}".format(port), db.db, db.db.STATE_DB) - except JsonPatchConflict: - ctx.fail("{} invalid or does not exist, or {} is not a member of {}".format(vlan, port, vlan)) + for vid in vid_list: + + log.log_info("'vlan member del {} {}' executing...".format(vid, port)) + + if not clicommon.is_vlanid_in_range(vid): + ctx.fail("Invalid VLAN ID {} (2-4094)".format(vid)) + + vlan = 'Vlan{}'.format(vid) + if clicommon.check_if_vlanid_exist(db.cfgdb, vlan) == False: + log.log_info("{} does not exist, Aborting!!!".format(vlan)) + ctx.fail("{} does not exist, Aborting!!!".format(vlan)) + + if clicommon.get_interface_naming_mode() == "alias": # TODO: MISSING CONSTRAINT IN YANG MODEL + alias = port + iface_alias_converter = clicommon.InterfaceAliasConverter(db) + port = iface_alias_converter.alias_to_name(alias) + if port is None: + ctx.fail("cannot find port name for alias {}".format(alias)) + + # TODO: MISSING CONSTRAINT IN YANG MODEL + if not clicommon.is_port_vlan_member(db.cfgdb, port, vlan): + ctx.fail("{} is not a member of {}".format(port, vlan)) + + + try: + config_db.set_entry('VLAN_MEMBER', (vlan, port), None) + delete_db_entry("DHCPv6_COUNTER_TABLE|{}".format(port), db.db, db.db.STATE_DB) + delete_db_entry("DHCP_COUNTER_TABLE|{}".format(port), db.db, db.db.STATE_DB) + except JsonPatchConflict: + ctx.fail("{} invalid or does not exist, or {} is not a member of {}".format(vlan, port, vlan)) diff --git a/doc/Command-Reference.md b/doc/Command-Reference.md index 30f317be80..322d0a6f61 100644 --- a/doc/Command-Reference.md +++ b/doc/Command-Reference.md @@ -162,6 +162,8 @@ * [Subinterfaces](#subinterfaces) * [Subinterfaces Show Commands](#subinterfaces-show-commands) * [Subinterfaces Config Commands](#subinterfaces-config-commands) +* [Switchport Modes](#switchport-modes) + * [Switchport Modes Config Commands](#switchportmodes-config-commands) * [Syslog](#syslog) * [Syslog show commands](#syslog-show-commands) * [Syslog config commands](#syslog-config-commands) @@ -4471,6 +4473,7 @@ Subsequent pages explain each of these commands in detail. neighbor Show neighbor related information portchannel Show PortChannel information status Show Interface status information + switchport Show Interface switchport information tpid Show Interface tpid information transceiver Show SFP Transceiver information ``` @@ -4936,6 +4939,53 @@ This command displays some more fields such as Lanes, Speed, MTU, Type, Asymmetr Ethernet180 105,106,107,108 100G 9100 hundredGigE46 down down N/A N/A ``` + +**show interface switchport status** + +This command displays switchport modes status of the interfaces + +- Usage: + ``` + show interfaces switchport status + ``` + +- Example (show interface switchport status of all interfaces): + ``` + admin@sonic:~$ show interfaces switchport status + Interface Mode + ----------- -------- + Ethernet0 access + Ethernet4 trunk + Ethernet8 routed + + ``` + +**show interface switchport config** + +This command displays switchport modes configuration of the interfaces + +- Usage: + ``` + show interfaces switchport config + ``` + +- Example (show interface switchport config of all interfaces): + ``` + admin@sonic:~$ show interfaces switchport config + Interface Mode Untagged Tagged + ----------- -------- -------- ------- + Ethernet0 access 2 + Ethernet4 trunk 3 4,5,6 + Ethernet8 routed + + ``` + + +For details please refer [Switchport Mode HLD](https://github.com/sonic-net/SONiC/pull/912/files#diff-03597c34684d527192f76a6e975792fcfc83f54e20dde63f159399232d148397) to know more about this command. + + + + **show interfaces transceiver** This command is already explained [here](#Transceivers) @@ -10072,6 +10122,41 @@ This sub-section explains how to configure subinterfaces. Go Back To [Beginning of the document](#) or [Beginning of this section](#subinterfaces) + + +## Switchport Modes + +### Switchport Modes Config Commands + +This subsection explains how to configure switchport modes on a Port/PortChannel. + +**config switchport mode ** + +Usage: + ``` + config switchport mode + ``` + +- Example (Config switchport mode access on "Ethernet0): + ``` + admin@sonic:~$ sudo config switchport mode access Ethernet0 + ``` + +- Example (Config switchport mode trunk on "Ethernet4"): + ``` + admin@sonic:~$ sudo config switchport mode trunk Ethernet4 + ``` + +- Example (Config switchport mode routed on "Ethernet12"): + ``` + admin@sonic:~$ sudo config switchport mode routed Ethernet12 + ``` + + + +Go Back To [Beginning of the document](#) or [Beginning of this section](#switchport-modes) + + ## Syslog ### Syslog Show Commands @@ -10784,6 +10869,31 @@ This command is used to add or delete the vlan. admin@sonic:~$ sudo config vlan add 100 ``` + +**config vlan add/del -m** + +This command is used to add or delete multiple vlans via single command. + +- Usage: + ``` + config vlan (add | del) -m + ``` + +- Example01 (Create the VLAN "Vlan100, Vlan101, Vlan102, Vlan103" if these does not already exist) + + ``` + admin@sonic:~$ sudo config vlan add -m 100-103 + ``` + + +- Example02 (Create the VLAN "Vlan105, Vlan106, Vlan107, Vlan108" if these does not already exist): + + ``` + admin@sonic:~$ sudo config vlan add -m 105,106,107,108 + ``` + + + **config vlan member add/del** This command is to add or delete a member port into the already created vlan. @@ -10805,6 +10915,49 @@ This command is to add or delete a member port into the already created vlan. This command will add Ethernet4 as member of the vlan 100. ``` + +**config vlan member add/del -m -e** + +This command is to add or delete a member port into multiple already created vlans. + +- Usage: + ``` + config vlan member add/del [-m] [-e] + ``` + +*NOTE: -m flag multiple Vlans in range or comma separted list can be added as a member port.* + + +*NOTE: -e is used as an except flag as explained with examples below.* + + +- Example: + ``` + admin@sonic:~$ sudo config vlan member add -m 100-103 Ethernet0 + This command will add Ethernet0 as member of the vlan 100, vlan 101, vlan 102, vlan 103 + ``` + + ``` + admin@sonic:~$ sudo config vlan member add -m 100,101,102 Ethernet4 + This command will add Ethernet4 as member of the vlan 100, vlan 101, vlan 102 + ``` + + ``` + admin@sonic:~$ sudo config vlan member add -e -m 104,105 Ethernet8 + Suppose vlan 100, vlan 101, vlan 102, vlan 103, vlan 104, vlan 105 are exisiting vlans. This command will add Ethernet8 as member of vlan 100, vlan 101, vlan 102, vlan 103 + ``` + + ``` + admin@sonic:~$ sudo config vlan member add -e 100 Ethernet12 + Suppose vlan 100, vlan 101, vlan 102, vlan 103, vlan 104, vlan 105 are exisiting vlans. This command will add Ethernet12 as member of vlan 101, vlan 102, vlan 103, vlan 104, vlan 105 + ``` + + ``` + admin@sonic:~$ sudo config vlan member add all Ethernet20 + Suppose vlan 100, vlan 101, vlan 102, vlan 103, vlan 104, vlan 105 are exisiting vlans. This command will add Ethernet20 as member of vlan 100, vlan 101, vlan 102, vlan 103, vlan 104, vlan 105 + ``` + + **config proxy_arp enabled/disabled** This command is used to enable or disable proxy ARP for a VLAN interface diff --git a/scripts/db_migrator.py b/scripts/db_migrator.py index 9667eedaff..190148f91a 100755 --- a/scripts/db_migrator.py +++ b/scripts/db_migrator.py @@ -509,6 +509,39 @@ def migrate_config_db_port_table_for_auto_neg(self): self.configDB.set(self.configDB.CONFIG_DB, '{}|{}'.format(table_name, key), 'adv_speeds', value['speed']) elif value['autoneg'] == '0': self.configDB.set(self.configDB.CONFIG_DB, '{}|{}'.format(table_name, key), 'autoneg', 'off') + + + def migrate_config_db_switchport_mode(self): + port_table = self.configDB.get_table('PORT') + portchannel_table = self.configDB.get_table('PORTCHANNEL') + vlan_member_table = self.configDB.get_table('VLAN_MEMBER') + + vlan_member_keys= [] + for _,key in vlan_member_table: + vlan_member_keys.append(key) + + for p_key, p_value in port_table.items(): + if 'mode' in p_value: + self.configDB.set(self.configDB.CONFIG_DB, '{}|{}'.format("PORT", p_key), 'mode', p_value['mode']) + else: + if p_key in vlan_member_keys: + p_value["mode"] = "trunk" + self.configDB.set_entry("PORT", p_key, p_value) + else: + p_value["mode"] = "routed" + self.configDB.set_entry("PORT", p_key, p_value) + + for pc_key, pc_value in portchannel_table.items(): + if 'mode' in pc_value: + self.configDB.set(self.configDB.CONFIG_DB, '{}|{}'.format("PORTCHANNEL", pc_key), 'mode', pc_value['mode']) + else: + if pc_key in vlan_member_keys: + pc_value["mode"] = "trunk" + self.configDB.set_entry("PORTCHANNEL", pc_key, pc_value) + else: + pc_value["mode"] = "routed" + self.configDB.set_entry("PORTCHANNEL", pc_key, pc_value) + def migrate_qos_db_fieldval_reference_remove(self, table_list, db, db_num, db_delimeter): for pair in table_list: diff --git a/show/interfaces/__init__.py b/show/interfaces/__init__.py index a5a3734664..497950b80e 100644 --- a/show/interfaces/__init__.py +++ b/show/interfaces/__init__.py @@ -797,3 +797,70 @@ def fec_status(interfacename, namespace, display, verbose): cmd += ['-n', str(namespace)] clicommon.run_command(cmd, display_cmd=verbose) + + +# +# switchport group (show interfaces switchport ...) +# +@interfaces.group(name='switchport', cls=clicommon.AliasedGroup) +def switchport(): + """Show interface switchport information""" + pass + + +@switchport.command(name="config") +@clicommon.pass_db +def switchport_mode_config(db): + """Show interface switchport config information""" + + port_data = list(db.cfgdb.get_table('PORT').keys()) + portchannel_data = list(db.cfgdb.get_table('PORTCHANNEL').keys()) + + portchannel_member_table = db.cfgdb.get_table('PORTCHANNEL_MEMBER') + + for interface in port_data: + if clicommon.interface_is_in_portchannel(portchannel_member_table,interface): + port_data.remove(interface) + + + keys = port_data + portchannel_data + + def tablelize(keys): + table = [] + + for key in natsorted(keys): + r = [clicommon.get_interface_name_for_display(db, key), clicommon.get_interface_switchport_mode(db,key), clicommon.get_interface_untagged_vlan_members(db,key), clicommon.get_interface_tagged_vlan_members(db,key)] + table.append(r) + + return table + + header = ['Interface', 'Mode', 'Untagged', 'Tagged'] + click.echo(tabulate(tablelize(keys), header, tablefmt="simple", stralign='left')) + +@switchport.command(name="status") +@clicommon.pass_db +def switchport_mode_status(db): + """Show interface switchport status information""" + + port_data = list(db.cfgdb.get_table('PORT').keys()) + portchannel_data = list(db.cfgdb.get_table('PORTCHANNEL').keys()) + + portchannel_member_table = db.cfgdb.get_table('PORTCHANNEL_MEMBER') + + for interface in port_data: + if clicommon.interface_is_in_portchannel(portchannel_member_table,interface): + port_data.remove(interface) + + keys = port_data + portchannel_data + + def tablelize(keys): + table = [] + + for key in natsorted(keys): + r = [clicommon.get_interface_name_for_display(db, key), clicommon.get_interface_switchport_mode(db,key)] + table.append(r) + + return table + + header = ['Interface', 'Mode'] + click.echo(tabulate(tablelize(keys), header,tablefmt="simple", stralign='left')) diff --git a/tests/db_migrator_input/config_db/port-an-expected.json b/tests/db_migrator_input/config_db/port-an-expected.json index 1ef2cf4916..14bdc415f4 100644 --- a/tests/db_migrator_input/config_db/port-an-expected.json +++ b/tests/db_migrator_input/config_db/port-an-expected.json @@ -5,6 +5,7 @@ "description": "etp1a", "mtu": "9100", "alias": "etp1a", + "mode": "routed", "pfc_asym": "off", "speed": "10000", "fec": "none", @@ -18,6 +19,7 @@ "admin_status": "up", "mtu": "9100", "alias": "etp1b", + "mode": "routed", "pfc_asym": "off", "speed": "25000", "fec": "none", @@ -30,6 +32,7 @@ "admin_status": "up", "mtu": "9100", "alias": "etp2a", + "mode": "routed", "pfc_asym": "off", "speed": "50000", "fec": "none" diff --git a/tests/db_migrator_input/config_db/portchannel-expected.json b/tests/db_migrator_input/config_db/portchannel-expected.json index 2644e5f4e9..874212b2f7 100644 --- a/tests/db_migrator_input/config_db/portchannel-expected.json +++ b/tests/db_migrator_input/config_db/portchannel-expected.json @@ -3,6 +3,7 @@ "admin_status": "up", "members@": "Ethernet0,Ethernet4", "min_links": "2", + "mode": "routed", "mtu": "9100", "lacp_key": "auto" }, @@ -10,6 +11,7 @@ "admin_status": "up", "members@": "Ethernet8,Ethernet12", "min_links": "2", + "mode": "routed", "mtu": "9100", "lacp_key": "auto" }, @@ -17,6 +19,7 @@ "admin_status": "up", "members@": "Ethernet16", "min_links": "1", + "mode": "routed", "mtu": "9100", "lacp_key": "auto" }, @@ -24,12 +27,14 @@ "admin_status": "up", "members@": "Ethernet20,Ethernet24", "min_links": "2", + "mode": "routed", "mtu": "9100", "lacp_key": "auto" }, "PORTCHANNEL|PortChannel9999": { "admin_status": "up", "mtu": "9100", + "mode": "routed", "lacp_key": "auto" }, "VERSIONS|DATABASE": { diff --git a/tests/db_migrator_input/config_db/switchport-expected.json b/tests/db_migrator_input/config_db/switchport-expected.json new file mode 100644 index 0000000000..8242b6470b --- /dev/null +++ b/tests/db_migrator_input/config_db/switchport-expected.json @@ -0,0 +1,144 @@ +{ + "PORT|Ethernet0": { + "admin_status": "up", + "alias": "fortyGigE0/0", + "index": "0", + "lanes": "25,26,27,28", + "mode": "trunk", + "mtu": "9100", + "speed": "40000" + }, + "PORT|Ethernet4": { + "admin_status": "up", + "alias": "fortyGigE0/4", + "index": "1", + "lanes": "29,30,31,32", + "mode": "routed", + "mtu": "9100", + "speed": "40000" + }, + "PORT|Ethernet8": { + "admin_status": "up", + "alias": "fortyGigE0/8", + "index": "2", + "lanes": "33,34,35,36", + "mode": "trunk", + "mtu": "9100", + "speed": "40000" + }, + "PORT|Ethernet12": { + "admin_status": "up", + "alias": "fortyGigE0/12", + "index": "3", + "lanes": "37,38,39,40", + "mode": "trunk", + "mtu": "9100", + "speed": "40000" + }, + "PORT|Ethernet16": { + "admin_status": "up", + "alias": "fortyGigE0/16", + "index": "4", + "lanes": "45,46,47,48", + "mode": "routed", + "mtu": "9100", + "speed": "40000" + }, + "PORT|Ethernet20": { + "admin_status": "up", + "alias": "fortyGigE0/20", + "index": "5", + "lanes": "41,42,43,44", + "mode": "trunk", + "mtu": "9100", + "speed": "40000" + }, + + "VLAN|Vlan2": { + "vlanid": "2" + }, + "VLAN|Vlan3": { + "vlanid": "3" + }, + "VLAN|Vlan4": { + "vlanid": "4" + }, + "VLAN|Vlan5": { + "vlanid": "5" + }, + "VLAN|Vlan6": { + "vlanid": "6" + }, + "VLAN|Vlan7": { + "vlanid": "7" + }, + + + "VLAN_MEMBER|Vlan2|Ethernet0": { + "tagging_mode": "tagged" + }, + "VLAN_MEMBER|Vlan3|Ethernet8": { + "tagging_mode": "tagged" + }, + "VLAN_MEMBER|Vlan4|Ethernet0": { + "tagging_mode": "tagged" + }, + "VLAN_MEMBER|Vlan6|Ethernet0": { + "tagging_mode": "tagged" + }, + "VLAN_MEMBER|Vlan6|Ethernet8": { + "tagging_mode": "untagged" + }, + "VLAN_MEMBER|Vlan7|Ethernet8": { + "tagging_mode": "tagged" + }, + "VLAN_MEMBER|Vlan5|Ethernet8": { + "tagging_mode": "untagged" + }, + "VLAN_MEMBER|Vlan3|PortChannel0003": { + "tagging_mode": "untagged" + }, + "VLAN_MEMBER|Vlan8|PortChannel0002": { + "tagging_mode": "tagged" + }, + "VLAN_MEMBER|Vlan9|PortChannel0002": { + "tagging_mode": "tagged" + }, + + "PORTCHANNEL|PortChannel0001": { + "admin_status": "up", + "fast_rate": "false", + "lacp_key": "auto", + "min_links": "1", + "mode": "trunk", + "mtu": "9100" + }, + "PORTCHANNEL|PortChannel0002": { + "admin_status": "up", + "fast_rate": "false", + "lacp_key": "auto", + "min_links": "1", + "mode": "trunk", + "mtu": "9100" + }, + "PORTCHANNEL|PortChannel0003": { + "admin_status": "up", + "fast_rate": "false", + "lacp_key": "auto", + "min_links": "1", + "mode": "trunk", + "mtu": "9100" + }, + "PORTCHANNEL|PortChannel0004": { + "admin_status": "up", + "fast_rate": "false", + "lacp_key": "auto", + "min_links": "1", + "mode": "routed", + "mtu": "9100" + }, + + "VERSIONS|DATABASE": { + "VERSION": "version_4_0_1" + } +} \ No newline at end of file diff --git a/tests/db_migrator_input/config_db/switchport-input.json b/tests/db_migrator_input/config_db/switchport-input.json new file mode 100644 index 0000000000..9613f29e75 --- /dev/null +++ b/tests/db_migrator_input/config_db/switchport-input.json @@ -0,0 +1,138 @@ +{ + "PORT|Ethernet0": { + "admin_status": "up", + "alias": "fortyGigE0/0", + "index": "0", + "lanes": "25,26,27,28", + "mtu": "9100", + "speed": "40000" + }, + "PORT|Ethernet4": { + "admin_status": "up", + "alias": "fortyGigE0/4", + "index": "1", + "lanes": "29,30,31,32", + "mode": "routed", + "mtu": "9100", + "speed": "40000" + }, + "PORT|Ethernet8": { + "admin_status": "up", + "alias": "fortyGigE0/8", + "index": "2", + "lanes": "33,34,35,36", + "mtu": "9100", + "speed": "40000" + }, + "PORT|Ethernet12": { + "admin_status": "up", + "alias": "fortyGigE0/12", + "index": "3", + "lanes": "37,38,39,40", + "mode": "access", + "mtu": "9100", + "speed": "40000" + }, + "PORT|Ethernet16": { + "admin_status": "up", + "alias": "fortyGigE0/16", + "index": "4", + "lanes": "45,46,47,48", + "mtu": "9100", + "speed": "40000" + }, + "PORT|Ethernet20": { + "admin_status": "up", + "alias": "fortyGigE0/20", + "index": "5", + "lanes": "41,42,43,44", + "mode": "trunk", + "mtu": "9100", + "speed": "40000" + }, + "VLAN|Vlan2": { + "vlanid": "2" + }, + "VLAN|Vlan3": { + "vlanid": "3" + }, + "VLAN|Vlan4": { + "vlanid": "4" + }, + "VLAN|Vlan5": { + "vlanid": "5" + }, + "VLAN|Vlan6": { + "vlanid": "6" + }, + "VLAN|Vlan7": { + "vlanid": "7" + }, + + "VLAN_MEMBER|Vlan2|Ethernet0": { + "tagging_mode": "tagged" + }, + "VLAN_MEMBER|Vlan3|Ethernet8": { + "tagging_mode": "tagged" + }, + "VLAN_MEMBER|Vlan4|Ethernet0": { + "tagging_mode": "tagged" + }, + "VLAN_MEMBER|Vlan6|Ethernet0": { + "tagging_mode": "tagged" + }, + "VLAN_MEMBER|Vlan6|Ethernet8": { + "tagging_mode": "untagged" + }, + "VLAN_MEMBER|Vlan7|Ethernet8": { + "tagging_mode": "tagged" + }, + "VLAN_MEMBER|Vlan5|Ethernet8": { + "tagging_mode": "untagged" + }, + "VLAN_MEMBER|Vlan3|PortChannel0003": { + "tagging_mode": "untagged" + }, + "VLAN_MEMBER|Vlan8|PortChannel0002": { + "tagging_mode": "tagged" + }, + "VLAN_MEMBER|Vlan9|PortChannel0002": { + "tagging_mode": "tagged" + }, + + + "PORTCHANNEL|PortChannel0001": { + "admin_status": "up", + "fast_rate": "false", + "lacp_key": "auto", + "min_links": "1", + "mode": "access", + "mtu": "9100" + }, + "PORTCHANNEL|PortChannel0002": { + "admin_status": "up", + "fast_rate": "false", + "lacp_key": "auto", + "min_links": "1", + "mode": "trunk", + "mtu": "9100" + }, + "PORTCHANNEL|PortChannel0003": { + "admin_status": "up", + "fast_rate": "false", + "lacp_key": "auto", + "min_links": "1", + "mtu": "9100" + }, + "PORTCHANNEL|PortChannel0004": { + "admin_status": "up", + "fast_rate": "false", + "lacp_key": "auto", + "min_links": "1", + "mtu": "9100" + }, + + "VERSIONS|DATABASE": { + "VERSION": "version_4_0_0" + } +} \ No newline at end of file diff --git a/tests/db_migrator_test.py b/tests/db_migrator_test.py index 84806e1905..d4cb50ff50 100644 --- a/tests/db_migrator_test.py +++ b/tests/db_migrator_test.py @@ -307,6 +307,33 @@ def test_port_autoneg_migrator(self): assert dbmgtr.configDB.get_table('PORT') == expected_db.cfgdb.get_table('PORT') assert dbmgtr.configDB.get_table('VERSIONS') == expected_db.cfgdb.get_table('VERSIONS') + + +class TestSwitchPortMigrator(object): + @classmethod + def setup_class(cls): + os.environ['UTILITIES_UNIT_TESTING'] = "2" + + @classmethod + def teardown_class(cls): + os.environ['UTILITIES_UNIT_TESTING'] = "0" + dbconnector.dedicated_dbs['CONFIG_DB'] = None + + def test_switchport_mode_migrator(self): + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'config_db', 'switchport-input') + import db_migrator + dbmgtr = db_migrator.DBMigrator(None) + dbmgtr.migrate() + + dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'config_db', 'switchport-expected') + expected_db = Db() + advance_version_for_expected_database(dbmgtr.configDB, expected_db.cfgdb, 'version_4_0_1') + + assert dbmgtr.configDB.get_table('PORT') == expected_db.cfgdb.get_table('PORT') + assert dbmgtr.configDB.get_table('PORTCHANNEL') == expected_db.cfgdb.get_table('PORTCHANNEL') + assert dbmgtr.configDB.get_table('VERSIONS') == expected_db.cfgdb.get_table('VERSIONS') + + class TestInitConfigMigrator(object): @classmethod def setup_class(cls): @@ -871,6 +898,7 @@ def test_golden_config_hostname(self): # hostname is from minigraph.xml assert hostname == 'SONiC-Dummy' + class TestMain(object): @classmethod def setup_class(cls): diff --git a/tests/interfaces_test.py b/tests/interfaces_test.py index c3246ba026..8f7d7301ef 100644 --- a/tests/interfaces_test.py +++ b/tests/interfaces_test.py @@ -144,6 +144,86 @@ 1001 PortChannel1001 N/A """ + +show_interfaces_switchport_status_output="""\ +Interface Mode +--------------- ------ +Ethernet0 routed +Ethernet4 trunk +Ethernet8 routed +Ethernet12 routed +Ethernet16 trunk +Ethernet20 routed +Ethernet24 trunk +Ethernet28 trunk +Ethernet36 routed +Ethernet40 routed +Ethernet44 routed +Ethernet48 routed +Ethernet52 access +Ethernet56 access +Ethernet60 routed +Ethernet64 routed +Ethernet68 routed +Ethernet72 routed +Ethernet76 routed +Ethernet80 routed +Ethernet84 routed +Ethernet88 routed +Ethernet92 routed +Ethernet96 routed +Ethernet100 routed +Ethernet104 routed +Ethernet108 routed +Ethernet116 routed +Ethernet124 routed +PortChannel0001 routed +PortChannel0002 routed +PortChannel0003 routed +PortChannel0004 routed +PortChannel1001 trunk +""" + +show_interfaces_switchport_config_output = """\ +Interface Mode Untagged Tagged +--------------- ------ ---------- -------- +Ethernet0 routed +Ethernet4 trunk 1000 +Ethernet8 routed 1000 +Ethernet12 routed 1000 +Ethernet16 trunk 1000 +Ethernet20 routed +Ethernet24 trunk 2000 +Ethernet28 trunk 2000 +Ethernet36 routed +Ethernet40 routed +Ethernet44 routed +Ethernet48 routed +Ethernet52 access +Ethernet56 access +Ethernet60 routed +Ethernet64 routed +Ethernet68 routed +Ethernet72 routed +Ethernet76 routed +Ethernet80 routed +Ethernet84 routed +Ethernet88 routed +Ethernet92 routed +Ethernet96 routed +Ethernet100 routed +Ethernet104 routed +Ethernet108 routed +Ethernet116 routed +Ethernet124 routed +PortChannel0001 routed +PortChannel0002 routed +PortChannel0003 routed +PortChannel0004 routed +PortChannel1001 trunk 4000 +""" + + class TestInterfaces(object): @classmethod def setup_class(cls): @@ -337,6 +417,26 @@ def test_parse_interface_in_filter(self): assert len(intf_list) == 3 assert intf_list == ["Ethernet-BP10", "Ethernet-BP11", "Ethernet-BP12"] + + def test_show_interfaces_switchport_status(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["interfaces"].commands["switchport"].commands["status"]) + print(result.exit_code) + print(result.output) + + assert result.exit_code == 0 + assert result.output == show_interfaces_switchport_status_output + + def test_show_interfaces_switchport_config(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["interfaces"].commands["switchport"].commands["config"]) + print(result.exit_code) + print(result.output) + + assert result.exit_code == 0 + assert result.output == show_interfaces_switchport_config_output + + @classmethod def teardown_class(cls): print("TEARDOWN") diff --git a/tests/ipv6_link_local_test.py b/tests/ipv6_link_local_test.py index 50b691be6b..bb9e53ac1a 100644 --- a/tests/ipv6_link_local_test.py +++ b/tests/ipv6_link_local_test.py @@ -232,7 +232,7 @@ def test_vlan_member_add_on_link_local_interface(self): result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], ["4000", "Ethernet40"], obj=obj) print(result.output) assert result.exit_code != 0 - assert 'Error: Ethernet40 is a router interface!' in result.output + assert 'Error: Ethernet40 is in routed mode!\nUse switchport mode command to change port mode' in result.output @classmethod def teardown_class(cls): diff --git a/tests/mock_tables/asic0/config_db.json b/tests/mock_tables/asic0/config_db.json index de20194a64..023e70299c 100644 --- a/tests/mock_tables/asic0/config_db.json +++ b/tests/mock_tables/asic0/config_db.json @@ -75,6 +75,7 @@ "admin_status": "up", "members@": "Ethernet0,Ethernet4", "min_links": "2", + "mode": "trunk", "mtu": "9100" }, "PORTCHANNEL|PortChannel4001": { diff --git a/tests/mock_tables/config_db.json b/tests/mock_tables/config_db.json index 2a81f96bfa..71fc64968b 100644 --- a/tests/mock_tables/config_db.json +++ b/tests/mock_tables/config_db.json @@ -30,6 +30,7 @@ "lanes": "25,26,27,28", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -41,6 +42,7 @@ "lanes": "29,30,31,32", "mtu": "9100", "tpid": "0x8100", + "mode": "trunk", "pfc_asym": "off", "speed": "40000" }, @@ -52,6 +54,7 @@ "lanes": "33,34,35,36", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -63,6 +66,7 @@ "lanes": "37,38,39,40", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -74,6 +78,7 @@ "lanes": "16", "mtu": "9100", "tpid": "0x8100", + "mode": "trunk", "pfc_asym": "off", "speed": "100" }, @@ -85,6 +90,7 @@ "lanes": "41,42,43,44", "mtu": "9100", "tpid": "0x9200", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -96,6 +102,7 @@ "lanes": "1,2,3,4", "mtu": "9100", "tpid": "0x8100", + "mode": "trunk", "pfc_asym": "off", "speed": "1000" }, @@ -107,6 +114,7 @@ "lanes": "5,6,7,8", "mtu": "9100", "tpid": "0x8100", + "mode": "trunk", "pfc_asym": "off", "speed": "1000" }, @@ -118,6 +126,7 @@ "lanes": "13,14,15,16", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -129,6 +138,7 @@ "lanes": "9,10,11,12", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "10" }, @@ -140,6 +150,7 @@ "lanes": "17,18,19,20", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -151,6 +162,7 @@ "lanes": "21,22,23,24", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -162,6 +174,7 @@ "lanes": "53,54,55,56", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -173,6 +186,7 @@ "lanes": "49,50,51,52", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -184,6 +198,7 @@ "lanes": "57,58,59,60", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -195,6 +210,7 @@ "lanes": "61,62,63,64", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -206,6 +222,7 @@ "lanes": "69,70,71,72", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -217,6 +234,7 @@ "lanes": "65,66,67,68", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -228,6 +246,7 @@ "lanes": "73,74,75,76", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -239,6 +258,7 @@ "lanes": "77,78,79,80", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -250,6 +270,7 @@ "lanes": "109,110,111,112", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -261,6 +282,7 @@ "lanes": "105,106,107,108", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -272,6 +294,7 @@ "lanes": "113,114,115,116", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -283,6 +306,7 @@ "lanes": "117,118,119,120", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -294,6 +318,7 @@ "lanes": "125,126,127,128", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -304,6 +329,7 @@ "lanes": "121,122,123,124", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -314,6 +340,7 @@ "lanes": "81,82,83,84", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -324,6 +351,7 @@ "lanes": "85,86,87,88", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -335,6 +363,7 @@ "lanes": "93,94,95,96", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -346,6 +375,7 @@ "lanes": "89,90,91,92", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -357,6 +387,7 @@ "lanes": "101,102,103,104", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -368,6 +399,7 @@ "lanes": "97,98,99,100", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000", "fec" : "auto" diff --git a/tests/vlan_test.py b/tests/vlan_test.py index 436e309281..02f7cb8d2b 100644 --- a/tests/vlan_test.py +++ b/tests/vlan_test.py @@ -134,6 +134,124 @@ +-----------+-----------------+-----------------+----------------+-------------+ """ + +test_config_add_del_multiple_vlan_and_vlan_member_output="""\ ++-----------+-----------------+-----------------+----------------+-------------+ +| VLAN ID | IP Address | Ports | Port Tagging | Proxy ARP | ++===========+=================+=================+================+=============+ +| 1000 | 192.168.0.1/21 | Ethernet4 | untagged | disabled | +| | fc02:1000::1/64 | Ethernet8 | untagged | | +| | | Ethernet12 | untagged | | +| | | Ethernet16 | untagged | | ++-----------+-----------------+-----------------+----------------+-------------+ +| 1001 | | Ethernet20 | tagged | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +| 1002 | | Ethernet20 | tagged | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +| 1003 | | Ethernet20 | tagged | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +| 2000 | 192.168.0.10/21 | Ethernet24 | untagged | enabled | +| | fc02:1011::1/64 | Ethernet28 | untagged | | ++-----------+-----------------+-----------------+----------------+-------------+ +| 3000 | | | | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +| 4000 | | PortChannel1001 | tagged | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +""" + +test_config_add_del_add_vlans_and_add_all_vlan_member_output="""\ ++-----------+-----------------+-----------------+----------------+-------------+ +| VLAN ID | IP Address | Ports | Port Tagging | Proxy ARP | ++===========+=================+=================+================+=============+ +| 1000 | 192.168.0.1/21 | Ethernet4 | untagged | disabled | +| | fc02:1000::1/64 | Ethernet8 | untagged | | +| | | Ethernet12 | untagged | | +| | | Ethernet16 | untagged | | +| | | Ethernet20 | tagged | | ++-----------+-----------------+-----------------+----------------+-------------+ +| 1001 | | Ethernet20 | tagged | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +| 1002 | | Ethernet20 | tagged | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +| 1003 | | Ethernet20 | tagged | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +| 2000 | 192.168.0.10/21 | Ethernet20 | tagged | enabled | +| | fc02:1011::1/64 | Ethernet24 | untagged | | +| | | Ethernet28 | untagged | | ++-----------+-----------------+-----------------+----------------+-------------+ +| 3000 | | Ethernet20 | tagged | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +| 4000 | | Ethernet20 | tagged | disabled | +| | | PortChannel1001 | tagged | | ++-----------+-----------------+-----------------+----------------+-------------+ +""" + +test_config_add_del_add_vlans_and_add_vlans_member_except_vlan_output = """\ ++-----------+-----------------+-----------------+----------------+-------------+ +| VLAN ID | IP Address | Ports | Port Tagging | Proxy ARP | ++===========+=================+=================+================+=============+ +| 1000 | 192.168.0.1/21 | Ethernet4 | untagged | disabled | +| | fc02:1000::1/64 | Ethernet8 | untagged | | +| | | Ethernet12 | untagged | | +| | | Ethernet16 | untagged | | ++-----------+-----------------+-----------------+----------------+-------------+ +| 1001 | | Ethernet20 | tagged | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +| 1002 | | Ethernet20 | tagged | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +| 2000 | 192.168.0.10/21 | Ethernet20 | tagged | enabled | +| | fc02:1011::1/64 | Ethernet24 | untagged | | +| | | Ethernet28 | untagged | | ++-----------+-----------------+-----------------+----------------+-------------+ +| 3000 | | Ethernet20 | tagged | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +| 4000 | | PortChannel1001 | tagged | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +""" + +test_config_add_del_add_vlans_and_add_vlans_member_except_vlan__after_del_member_output = """\ ++-----------+-----------------+-----------------+----------------+-------------+ +| VLAN ID | IP Address | Ports | Port Tagging | Proxy ARP | ++===========+=================+=================+================+=============+ +| 1000 | 192.168.0.1/21 | Ethernet4 | untagged | disabled | +| | fc02:1000::1/64 | Ethernet8 | untagged | | +| | | Ethernet12 | untagged | | +| | | Ethernet16 | untagged | | ++-----------+-----------------+-----------------+----------------+-------------+ +| 1001 | | Ethernet20 | tagged | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +| 1002 | | Ethernet20 | tagged | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +| 2000 | 192.168.0.10/21 | Ethernet24 | untagged | enabled | +| | fc02:1011::1/64 | Ethernet28 | untagged | | ++-----------+-----------------+-----------------+----------------+-------------+ +| 3000 | | | | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +| 4000 | | PortChannel1001 | tagged | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +""" + +test_config_add_del_vlan_and_vlan_member_with_switchport_modes_output = """\ ++-----------+-----------------+-----------------+----------------+-------------+ +| VLAN ID | IP Address | Ports | Port Tagging | Proxy ARP | ++===========+=================+=================+================+=============+ +| 1000 | 192.168.0.1/21 | Ethernet4 | untagged | disabled | +| | fc02:1000::1/64 | Ethernet8 | untagged | | +| | | Ethernet12 | untagged | | +| | | Ethernet16 | untagged | | +| | | Ethernet20 | tagged | | ++-----------+-----------------+-----------------+----------------+-------------+ +| 1001 | | Ethernet20 | untagged | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +| 2000 | 192.168.0.10/21 | Ethernet24 | untagged | enabled | +| | fc02:1011::1/64 | Ethernet28 | untagged | | ++-----------+-----------------+-----------------+----------------+-------------+ +| 3000 | | | | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +| 4000 | | PortChannel1001 | tagged | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +""" + config_add_del_vlan_and_vlan_member_in_alias_mode_output="""\ +-----------+-----------------+-----------------+----------------+-------------+ | VLAN ID | IP Address | Ports | Port Tagging | Proxy ARP | @@ -155,6 +273,28 @@ """ + +test_config_add_del_vlan_and_vlan_member_with_switchport_modes_and_change_mode_types_output = """\ ++-----------+-----------------+-----------------+----------------+-------------+ +| VLAN ID | IP Address | Ports | Port Tagging | Proxy ARP | ++===========+=================+=================+================+=============+ +| 1000 | 192.168.0.1/21 | Ethernet4 | untagged | disabled | +| | fc02:1000::1/64 | Ethernet8 | untagged | | +| | | Ethernet12 | untagged | | +| | | Ethernet16 | untagged | | ++-----------+-----------------+-----------------+----------------+-------------+ +| 1001 | | | | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +| 2000 | 192.168.0.10/21 | Ethernet24 | untagged | enabled | +| | fc02:1011::1/64 | Ethernet28 | untagged | | ++-----------+-----------------+-----------------+----------------+-------------+ +| 3000 | | | | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +| 4000 | | PortChannel1001 | tagged | disabled | ++-----------+-----------------+-----------------+----------------+-------------+ +""" + + class TestVlan(object): _old_run_bgp_command = None @classmethod @@ -236,7 +376,7 @@ def test_config_vlan_add_vlan_with_invalid_vlanid(self): print(result.exit_code) print(result.output) assert result.exit_code != 0 - assert "Error: Invalid VLAN ID 4096 (1-4094)" in result.output + assert "Error: Invalid VLAN ID 4096 (2-4094)" in result.output def test_config_vlan_add_vlan_with_exist_vlanid(self): runner = CliRunner() @@ -252,7 +392,7 @@ def test_config_vlan_del_vlan_with_invalid_vlanid(self): print(result.exit_code) print(result.output) assert result.exit_code != 0 - assert "Error: Invalid VLAN ID 4096 (1-4094)" in result.output + assert "Error: Invalid VLAN ID 4096 (2-4094)" in result.output def test_config_vlan_del_vlan_with_nonexist_vlanid(self): runner = CliRunner() @@ -262,13 +402,80 @@ def test_config_vlan_del_vlan_with_nonexist_vlanid(self): assert result.exit_code != 0 assert "Error: Vlan1001 does not exist" in result.output + + def test_config_vlan_add_vlan_with_multiple_vlanids(self, mock_restart_dhcp_relay_service): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["10,20,30,40", "--multiple"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + def test_config_vlan_add_vlan_with_multiple_vlanids_with_range(self, mock_restart_dhcp_relay_service): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["10-20", "--multiple"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + def test_config_vlan_add_vlan_with_multiple_vlanids_with_range_and_multiple_ids(self, mock_restart_dhcp_relay_service): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["10-15,20,25,30", "--multiple"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + def test_config_vlan_add_vlan_with_wrong_range(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["15-10", "--multiple"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "15 is greater than 10. List cannot be generated" in result.output + + def test_config_vlan_add_vlan_range_with_default_vlan(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["1-10", "--multiple"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Vlan1 is default vlan" in result.output + + def test_config_vlan_add_vlan_is_digit_fail(self): + runner = CliRunner() + vid = "test_fail_case" + result = runner.invoke(config.config.commands["vlan"].commands["add"], [vid]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "{} is not integer".format(vid) in result.output + + def test_config_vlan_add_vlan_is_default_vlan(self): + runner = CliRunner() + default_vid = "1" + vlan = "Vlan{}".format(default_vid) + result = runner.invoke(config.config.commands["vlan"].commands["add"], [default_vid]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "{} is default VLAN.".format(vlan) in result.output + + def test_config_vlan_del_vlan_does_not_exist(self): + runner = CliRunner() + vid = "3010" + vlan = "Vlan{}".format(vid) + result = runner.invoke(config.config.commands["vlan"].commands["del"], [vid]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "{} does not exist".format(vlan) in result.output + def test_config_vlan_add_member_with_invalid_vlanid(self): runner = CliRunner() result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], ["4096", "Ethernet4"]) print(result.exit_code) print(result.output) assert result.exit_code != 0 - assert "Error: Invalid VLAN ID 4096 (1-4094)" in result.output + assert "Error: Invalid VLAN ID 4096 (2-4094)" in result.output def test_config_vlan_add_member_with_nonexist_vlanid(self): runner = CliRunner() @@ -294,8 +501,16 @@ def test_config_vlan_add_nonexist_port_member(self): assert result.exit_code != 0 assert "Error: Ethernet3 does not exist" in result.output + def test_config_vlan_add_nonexist_portchannel_member(self): runner = CliRunner() + #switch port mode for PortChannel1011 to trunk mode + result = runner.invoke(config.config.commands["switchport"].commands["mode"],["trunk", "PortChannel1011"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: PortChannel1011 does not exist" in result.output + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], \ ["1000", "PortChannel1011"]) print(result.exit_code) @@ -303,6 +518,7 @@ def test_config_vlan_add_nonexist_portchannel_member(self): assert result.exit_code != 0 assert "Error: PortChannel1011 does not exist" in result.output + def test_config_vlan_add_portchannel_member(self): runner = CliRunner() db = Db() @@ -313,6 +529,7 @@ def test_config_vlan_add_portchannel_member(self): print(result.output) assert result.exit_code == 0 + # show output result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) print(result.exit_code) @@ -329,7 +546,7 @@ def test_config_vlan_add_rif_portchannel_member(self): print(result.exit_code) print(result.output) assert result.exit_code != 0 - assert "Error: PortChannel0001 is a router interface!" in result.output + assert "Error: PortChannel0001 is in routed mode!\nUse switchport mode command to change port mode" in result.output def test_config_vlan_with_vxlanmap_del_vlan(self, mock_restart_dhcp_relay_service): runner = CliRunner() @@ -464,6 +681,15 @@ def test_config_add_del_vlan_and_vlan_member(self, mock_restart_dhcp_relay_servi print(result.output) traceback.print_tb(result.exc_info[2]) assert result.exit_code == 0 + assert "Ethernet20 is in routed mode!\nUse switchport mode command to change port mode" in result.output + + # configure Ethernet20 from routed to access mode + result = runner.invoke(config.config.commands["switchport"].commands["mode"],["access", "Ethernet20"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert "Ethernet20 switched from routed to access mode" in result.output + # show output result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) @@ -510,6 +736,8 @@ def test_config_add_del_vlan_and_vlan_member_in_alias_mode(self, mock_restart_dh traceback.print_tb(result.exc_info[2]) assert result.exit_code == 0 + + # show output result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) print(result.exit_code) @@ -538,6 +766,385 @@ def test_config_add_del_vlan_and_vlan_member_in_alias_mode(self, mock_restart_dh os.environ['SONIC_CLI_IFACE_MODE'] = "default" + + def test_config_add_del_multiple_vlan_and_vlan_member(self,mock_restart_dhcp_relay_service): + runner = CliRunner() + db = Db() + + # add vlan 1001 + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["1001,1002,1003","--multiple"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # add Ethernet20 to vlan1001, vlan1002, vlan1003 multiple flag but Ethernet20 is in routed mode will give error + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["1001,1002,1003", "Ethernet20", "--multiple"], obj=db) + print(result.exit_code) + print(result.output) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code != 0 + assert "Ethernet20 is in routed mode!\nUse switchport mode command to change port mode" in result.output + + # configure Ethernet20 from routed to access mode + result = runner.invoke(config.config.commands["switchport"].commands["mode"],["trunk", "Ethernet20"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert "Ethernet20 switched from routed to trunk mode" in result.output + + # add Ethernet20 to vlan 1001 + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["1001,1002,1003", "Ethernet20", "--multiple"], obj=db) + print(result.exit_code) + print(result.output) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code == 0 + + # show output + result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) + print(result.output) + assert result.output == test_config_add_del_multiple_vlan_and_vlan_member_output + + # remove vlan member + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], + ["1001-1003", "Ethernet20", "--multiple"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # add del 1001 + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001-1003","--multiple"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # show output + result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vlan_brief_output + + def test_config_add_del_add_vlans_and_add_vlans_member_except_vlan(self, mock_restart_dhcp_relay_service): + runner = CliRunner() + db = Db() + + # add vlan 1001 + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["1001,1002","--multiple"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # add Ethernet20 to vlan1001, vlan1002, vlan1003 multiple flag but Ethernet20 is in routed mode will give error + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["1000,4000", "Ethernet20", "--multiple", "--except_flag"], obj=db) + print(result.exit_code) + print(result.output) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code != 0 + assert "Ethernet20 is in routed mode!\nUse switchport mode command to change port mode" in result.output + + # configure Ethernet20 from routed to access mode + result = runner.invoke(config.config.commands["switchport"].commands["mode"],["trunk", "Ethernet20"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert "Ethernet20 switched from routed to trunk mode" in result.output + + # add Ethernet20 to vlan 1001 + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["1000,4000", "Ethernet20", "--multiple", "--except_flag"], obj=db) + print(result.exit_code) + print(result.output) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code == 0 + + # show output + result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) + print(result.output) + assert result.output == test_config_add_del_add_vlans_and_add_vlans_member_except_vlan_output + + # remove vlan member except some + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], + ["1001,1002", "Ethernet20", "--multiple", "--except_flag"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # show output + result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == test_config_add_del_add_vlans_and_add_vlans_member_except_vlan__after_del_member_output + + # remove vlan member + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], + ["1001,1002", "Ethernet20", "--multiple"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # add del 1001 + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001-1002","--multiple"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # show output + result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vlan_brief_output + + + def test_config_add_del_add_vlans_and_add_all_vlan_member(self, mock_restart_dhcp_relay_service): + runner = CliRunner() + db = Db() + + # add vlan 1001 + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["1001,1002,1003","--multiple"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # add Ethernet20 to vlan1001, vlan1002, vlan1003 multiple flag but Ethernet20 is in routed mode will give error + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["all", "Ethernet20"], obj=db) + print(result.exit_code) + print(result.output) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code != 0 + assert "Ethernet20 is in routed mode!\nUse switchport mode command to change port mode" in result.output + + # configure Ethernet20 from routed to access mode + result = runner.invoke(config.config.commands["switchport"].commands["mode"],["trunk", "Ethernet20"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert "Ethernet20 switched from routed to trunk mode" in result.output + + # add Ethernet20 to vlan 1001 + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["all", "Ethernet20"], obj=db) + print(result.exit_code) + print(result.output) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code == 0 + + # show output + result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) + print(result.output) + assert result.output == test_config_add_del_add_vlans_and_add_all_vlan_member_output + + # remove vlan member + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], + ["all", "Ethernet20"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # add del 1001 + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001-1003","--multiple"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # show output + result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vlan_brief_output + + def test_config_add_del_vlan_and_vlan_member_with_switchport_modes(self, mock_restart_dhcp_relay_service): + runner = CliRunner() + db = Db() + + # add vlan 1001 + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["1001"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # add Ethernet20 to vlan 1001 but Ethernet20 is in routed mode will give error + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["1001", "Ethernet20", "--untagged"], obj=db) + print(result.exit_code) + print(result.output) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code != 0 + assert "Ethernet20 is in routed mode!\nUse switchport mode command to change port mode" in result.output + + + # configure Ethernet20 from routed to access mode + result = runner.invoke(config.config.commands["switchport"].commands["mode"],["access", "Ethernet20"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert "Ethernet20 switched from routed to access mode" in result.output + + # add Ethernet20 to vlan 1001 + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["1001", "Ethernet20", "--untagged"], obj=db) + print(result.exit_code) + print(result.output) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code == 0 + + # add Ethernet20 to vlan 1001 as tagged member + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["1000", "Ethernet20"], obj=db) + print(result.exit_code) + print(result.output) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code != 0 + assert "Ethernet20 is in access mode! Tagged Members cannot be added" in result.output + + # configure Ethernet20 from access to trunk mode + result = runner.invoke(config.config.commands["switchport"].commands["mode"],["trunk", "Ethernet20"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert "Ethernet20 switched from access to trunk mode" in result.output + + # add Ethernet20 to vlan 1001 as tagged member + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["1000", "Ethernet20"], obj=db) + print(result.exit_code) + print(result.output) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code == 0 + + # show output + result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) + print(result.output) + assert result.output == test_config_add_del_vlan_and_vlan_member_with_switchport_modes_output + + # configure Ethernet20 from trunk to routed mode + result = runner.invoke(config.config.commands["switchport"].commands["mode"],["routed", "Ethernet20"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Ethernet20 has tagged member(s). \nRemove them to change mode to routed" in result.output + + # remove vlan member + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], + ["1000", "Ethernet20"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # configure Ethernet20 from trunk to routed mode + result = runner.invoke(config.config.commands["switchport"].commands["mode"],["routed", "Ethernet20"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Ethernet20 has untagged member. \nRemove it to change mode to routed" in result.output + + # remove vlan member + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], + ["1001", "Ethernet20"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # configure Ethernet20 from trunk to routed mode + result = runner.invoke(config.config.commands["switchport"].commands["mode"],["routed", "Ethernet20"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert "Ethernet20 switched from trunk to routed mode" in result.output + + # add del 1001 + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # show output + result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vlan_brief_output + + + def test_config_add_del_vlan_and_vlan_member_with_switchport_modes_and_change_mode_types(self, mock_restart_dhcp_relay_service): + runner = CliRunner() + db = Db() + + # add vlan 1001 + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["1001"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + + # configure Ethernet64 to routed mode + result = runner.invoke(config.config.commands["switchport"].commands["mode"],["routed", "Ethernet64"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # add Ethernet64 to vlan 1001 but Ethernet64 is in routed mode will give error + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["1001", "Ethernet64"], obj=db) + print(result.exit_code) + print(result.output) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code != 0 + assert "Ethernet64 is in routed mode!\nUse switchport mode command to change port mode" in result.output + + # configure Ethernet64 from routed to trunk mode + result = runner.invoke(config.config.commands["switchport"].commands["mode"],["trunk", "Ethernet64"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert "Ethernet64 switched from routed to trunk mode" in result.output + + # add Ethernet64 to vlan 1001 + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["1001", "Ethernet64"], obj=db) + print(result.exit_code) + print(result.output) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code == 0 + + # configure Ethernet64 from routed to access mode + result = runner.invoke(config.config.commands["switchport"].commands["mode"],["access", "Ethernet64"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Ethernet64 is in trunk mode and have tagged member(s).\nRemove tagged member(s) from Ethernet64 to switch to access mode" in result.output + + # remove vlan member + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], + ["1001", "Ethernet64"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # configure Ethernet64 from routed to access mode + result = runner.invoke(config.config.commands["switchport"].commands["mode"],["access", "Ethernet64"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert "Ethernet64 switched from trunk to access mode" in result.output + + # show output + result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == test_config_add_del_vlan_and_vlan_member_with_switchport_modes_and_change_mode_types_output + + + + + def test_config_vlan_proxy_arp_with_nonexist_vlan_intf_table(self): modes = ["enabled", "disabled"] runner = CliRunner() @@ -548,7 +1155,8 @@ def test_config_vlan_proxy_arp_with_nonexist_vlan_intf_table(self): result = runner.invoke(config.config.commands["vlan"].commands["proxy_arp"], ["1000", mode], obj=db) print(result.exit_code) - print(result.output) + print(result.out + put) assert result.exit_code != 0 assert "Interface Vlan1000 does not exist" in result.output @@ -627,7 +1235,7 @@ def test_config_set_router_port_on_member_interface(self): ["Ethernet4", "10.10.10.1/24"], obj=obj) print(result.exit_code, result.output) assert result.exit_code == 0 - assert 'Interface Ethernet4 is a member of vlan' in result.output + assert 'Interface Ethernet4 is not in routed mode!' in result.output def test_config_vlan_add_member_of_portchannel(self): runner = CliRunner() diff --git a/utilities_common/cli.py b/utilities_common/cli.py index 9d3cdae710..d70e0ba949 100644 --- a/utilities_common/cli.py +++ b/utilities_common/cli.py @@ -188,6 +188,159 @@ def alias_to_name(self, interface_alias): # Lazy global class instance for SONiC interface name to alias conversion iface_alias_converter = lazy_object_proxy.Proxy(lambda: InterfaceAliasConverter()) +def vlan_range_list(ctx, vid_range: str) -> list: + + vid1, vid2 = map(int, vid_range.split("-")) + + if vid1 == 1 or vid2 == 1: + ctx.fail("Vlan1 is default vlan") + + if vid1 >= vid2: + ctx.fail("{} is greater than {}. List cannot be generated".format(vid1,vid2)) + + if is_vlanid_in_range(vid1) and is_vlanid_in_range(vid2): + return list(range(vid1, vid2+1)) + else: + ctx.fail("Invalid VLAN ID must be in (2-4094)") + + +def multiple_vlan_parser(ctx, s_input: str) -> list: + + vlan_list = [] + + vlan_map = map(str, s_input.replace(" ", "").split(",")) + for vlan in vlan_map: + if "-" in vlan: + vlan_list += vlan_range_list(ctx, vlan) + elif vlan.isdigit() and int(vlan) not in vlan_list: + vlan_list.append(int(vlan)) + elif not vlan.isdigit(): + ctx.fail("{} is not integer".format(vlan)) + + vlan_list.sort() + return vlan_list + + +def get_existing_vlan_id(db) -> list: + existing_vlans = [] + vlan_data = db.cfgdb.get_table('VLAN') + + for i in vlan_data.keys(): + existing_vlans.append(int(i.strip("Vlan"))) + + return sorted(existing_vlans) + +def get_existing_vlan_id_on_interface(db,port) -> list: + intf_vlans = [] + vlan_member_data = db.cfgdb.get_table('VLAN_MEMBER') + + for (k,v) in vlan_member_data.keys(): + if v == port: + intf_vlans.append(int(k.strip("Vlan"))) + + return sorted(intf_vlans) + + +def vlan_member_input_parser(ctx, command_mode, db, except_flag, multiple, vid, port) -> list: + vid_list = [] + if vid == "all": + if command_mode == "add": + return get_existing_vlan_id(db) # config vlan member add + if command_mode == "del": + return get_existing_vlan_id_on_interface(db,port) # config vlan member del + + if multiple: + vid_list = multiple_vlan_parser(ctx, vid) + + if except_flag: + if command_mode == "add": + comp_list = get_existing_vlan_id(db) # config vlan member add + + elif command_mode == "del": + comp_list = get_existing_vlan_id_on_interface(db,port) # config vlan member del + + if multiple: + for i in vid_list: + if i in comp_list: + comp_list.remove(i) + + else: + if not vid.isdigit(): + ctx.fail("Vlan is not integer.") + vid = int(vid) + if vid in comp_list: + comp_list.remove(vid) + vid_list = comp_list + + elif not multiple: + # if entered vlan is not a integer + if not vid.isdigit(): + ctx.fail("Vlan is not integer.") + vid_list.append(int(vid)) + + # sorting the vid_list + vid_list.sort() + return vid_list + +def interface_is_tagged_member(db, interface_name): + """ Check if interface has tagged members i.e. is in trunk mode""" + vlan_member_table = db.get_table('VLAN_MEMBER') + + for key, val in vlan_member_table.items(): + if(key[1] == interface_name): + if (val['tagging_mode'] == 'tagged'): + return True + return False + + def get_vlan_id(vlan): + vlan_prefix, vid = vlan.split('Vlan') + return vid + +def get_interface_name_for_display(db ,interface): + interface_naming_mode = get_interface_naming_mode() + iface_alias_converter = InterfaceAliasConverter(db) + if interface_naming_mode == "alias" and interface: + return iface_alias_converter.name_to_alias(interface) + return interface + +def get_interface_untagged_vlan_members(db,interface): + untagged_vlans = [] + vlan_member = db.cfgdb.get_table('VLAN_MEMBER') + + for member in natsorted(list(vlan_member.keys())): + interface_vlan, interface_name = member + + if interface == interface_name and vlan_member[member]['tagging_mode'] == 'untagged': + untagged_vlans.append(get_vlan_id(interface_vlan)) + + return "\n".join(untagged_vlans) + +def get_interface_tagged_vlan_members(db,interface): + tagged_vlans = [] + formatted_tagged_vlans = [] + vlan_member = db.cfgdb.get_table('VLAN_MEMBER') + + for member in natsorted(list(vlan_member.keys())): + interface_vlan, interface_name = member + + if interface == interface_name and vlan_member[member]['tagging_mode'] == 'tagged': + tagged_vlans.append(get_vlan_id(interface_vlan)) + + for i in range(len(tagged_vlans)//5+1): + formatted_tagged_vlans.append(" ,".join([str(x) for x in tagged_vlans[i*5:(i+1)*5]])) + + return "\n".join(formatted_tagged_vlans) + +def get_interface_switchport_mode(db, interface): + port = db.cfgdb.get_entry('PORT',interface) + portchannel = db.cfgdb.get_entry('PORTCHANNEL',interface) + switchport_mode = 'routed' + if "mode" in port: + switchport_mode = port['mode'] + elif "mode" in portchannel: + switchport_mode = portchannel['mode'] + return switchport_mode + def get_interface_naming_mode(): mode = os.getenv('SONIC_CLI_IFACE_MODE') if mode is None: From bdddd977dd8eec19c38b75e449f71cf34d671628 Mon Sep 17 00:00:00 2001 From: Saba Akram <126749695+sabakram@users.noreply.github.com> Date: Mon, 1 Jan 2024 08:31:26 +0000 Subject: [PATCH 02/19] Fix for cli.py --- utilities_common/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utilities_common/cli.py b/utilities_common/cli.py index d70e0ba949..aead848052 100644 --- a/utilities_common/cli.py +++ b/utilities_common/cli.py @@ -293,8 +293,8 @@ def interface_is_tagged_member(db, interface_name): return False def get_vlan_id(vlan): - vlan_prefix, vid = vlan.split('Vlan') - return vid + vlan_prefix, vid = vlan.split('Vlan') + return vid def get_interface_name_for_display(db ,interface): interface_naming_mode = get_interface_naming_mode() From 643d6a552a20c0fa123dc2de79a479c9e5fbac1d Mon Sep 17 00:00:00 2001 From: Saba Akram <126749695+sabakram@users.noreply.github.com> Date: Mon, 1 Jan 2024 10:56:08 +0000 Subject: [PATCH 03/19] Fix for indentation --- config/vlan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/vlan.py b/config/vlan.py index df98ad9324..2f721407ef 100644 --- a/config/vlan.py +++ b/config/vlan.py @@ -36,7 +36,7 @@ def is_dhcp_relay_running(): @click.option('-m', '--multiple', is_flag=True, help="Add Multiple Vlan(s) in Range or in Comma separated list") @clicommon.pass_db def add_vlan(db, vid, multiple): - """Add VLAN""" + """Add VLAN""" ctx = click.get_current_context() From 1c523b6ef6c4ffd0c1bb97314a179af8d690235d Mon Sep 17 00:00:00 2001 From: Saba Akram <126749695+sabakram@users.noreply.github.com> Date: Mon, 1 Jan 2024 11:19:01 +0000 Subject: [PATCH 04/19] Fix for error --- config/vlan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/vlan.py b/config/vlan.py index 2f721407ef..7464cbfcc7 100644 --- a/config/vlan.py +++ b/config/vlan.py @@ -230,7 +230,7 @@ def vlan_member(): @click.argument('vid', metavar='', required=True, type=int) @click.argument('port', metavar='port', required=True) @click.option('-u', '--untagged', is_flag=True, help="Untagged status") -@click.option('-m', '--multiple', is_flag=def add_vlan_member(db, vid, port, untagged, multiple, except_flag):True, help="Add Multiple Vlan(s) in Range or in Comma separated list") +@click.option('-m', '--multiple', is_flag==def add_vlan_member(db, vid, port, untagged, multiple, except_flag):True, help="Add Multiple Vlan(s) in Range or in Comma separated list") @click.option('-e', '--except_flag', is_flag=True, help="Skips the given vlans and adds all other existing vlans") @clicommon.pass_db def add_vlan_member(db, vid, port, untagged, multiple, except_flag): From 53afe45f4c0dcccee65123606f1abe02f12ce393 Mon Sep 17 00:00:00 2001 From: Saba Akram <126749695+sabakram@users.noreply.github.com> Date: Mon, 1 Jan 2024 11:46:42 +0000 Subject: [PATCH 05/19] Fix --- config/vlan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/vlan.py b/config/vlan.py index 7464cbfcc7..9fced1aa85 100644 --- a/config/vlan.py +++ b/config/vlan.py @@ -230,7 +230,7 @@ def vlan_member(): @click.argument('vid', metavar='', required=True, type=int) @click.argument('port', metavar='port', required=True) @click.option('-u', '--untagged', is_flag=True, help="Untagged status") -@click.option('-m', '--multiple', is_flag==def add_vlan_member(db, vid, port, untagged, multiple, except_flag):True, help="Add Multiple Vlan(s) in Range or in Comma separated list") +@click.option('-m', '--multiple', is_flag=True, help="Add Multiple Vlan(s) in Range or in Comma separated list") @click.option('-e', '--except_flag', is_flag=True, help="Skips the given vlans and adds all other existing vlans") @clicommon.pass_db def add_vlan_member(db, vid, port, untagged, multiple, except_flag): From 09c2a6fcdb1dfe20df6681037aa9f3fa114efb64 Mon Sep 17 00:00:00 2001 From: Saba Akram <126749695+sabakram@users.noreply.github.com> Date: Mon, 1 Jan 2024 12:31:04 +0000 Subject: [PATCH 06/19] Fixing indentation errors --- tests/vlan_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/vlan_test.py b/tests/vlan_test.py index 02f7cb8d2b..4da52db3d3 100644 --- a/tests/vlan_test.py +++ b/tests/vlan_test.py @@ -403,7 +403,7 @@ def test_config_vlan_del_vlan_with_nonexist_vlanid(self): assert "Error: Vlan1001 does not exist" in result.output - def test_config_vlan_add_vlan_with_multiple_vlanids(self, mock_restart_dhcp_relay_service): + def test_config_vlan_add_vlan_with_multiple_vlanids(self, mock_restart_dhcp_relay_service): runner = CliRunner() result = runner.invoke(config.config.commands["vlan"].commands["add"], ["10,20,30,40", "--multiple"]) print(result.exit_code) From e7bc92497f3ecff1e101e4c8fe783b88fa834a60 Mon Sep 17 00:00:00 2001 From: Saba Akram <126749695+sabakram@users.noreply.github.com> Date: Mon, 1 Jan 2024 13:03:55 +0000 Subject: [PATCH 07/19] Fix for error --- tests/vlan_test.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/vlan_test.py b/tests/vlan_test.py index 4da52db3d3..8a381ca8fe 100644 --- a/tests/vlan_test.py +++ b/tests/vlan_test.py @@ -1142,9 +1142,6 @@ def test_config_add_del_vlan_and_vlan_member_with_switchport_modes_and_change_mo assert result.output == test_config_add_del_vlan_and_vlan_member_with_switchport_modes_and_change_mode_types_output - - - def test_config_vlan_proxy_arp_with_nonexist_vlan_intf_table(self): modes = ["enabled", "disabled"] runner = CliRunner() @@ -1155,8 +1152,7 @@ def test_config_vlan_proxy_arp_with_nonexist_vlan_intf_table(self): result = runner.invoke(config.config.commands["vlan"].commands["proxy_arp"], ["1000", mode], obj=db) print(result.exit_code) - print(result.out - put) + print(result.output) assert result.exit_code != 0 assert "Interface Vlan1000 does not exist" in result.output From 3e869e59a89e6447e1044f49f3463334cb428bd5 Mon Sep 17 00:00:00 2001 From: Saba Akram <126749695+sabakram@users.noreply.github.com> Date: Mon, 8 Jan 2024 10:04:06 +0000 Subject: [PATCH 08/19] Fix for failures --- config/vlan.py | 10 +++++----- scripts/db_migrator.py | 13 +++++++++++-- .../config_db/port-an-expected.json | 2 +- .../db_migrator_input/config_db/port-an-input.json | 2 +- .../config_db/switchport-expected.json | 4 ++-- .../config_db/switchport-input.json | 2 +- tests/interfaces_test.py | 8 ++++---- tests/mock_tables/config_db.json | 3 ++- 8 files changed, 27 insertions(+), 17 deletions(-) diff --git a/config/vlan.py b/config/vlan.py index 9fced1aa85..1244b4f5ea 100644 --- a/config/vlan.py +++ b/config/vlan.py @@ -32,7 +32,7 @@ def is_dhcp_relay_running(): @vlan.command('add') -@click.argument('vid', metavar='', required=True, type=int) +@click.argument('vid', metavar='', required=True) @click.option('-m', '--multiple', is_flag=True, help="Add Multiple Vlan(s) in Range or in Comma separated list") @clicommon.pass_db def add_vlan(db, vid, multiple): @@ -98,13 +98,13 @@ def delete_db_entry(entry_name, db_connector, db_name): @vlan.command('del') -@click.argument('vid', metavar='', required=True, type=int) +@click.argument('vid', metavar='', required=True) @click.option('-m', '--multiple', is_flag=True, help="Add Multiple Vlan(s) in Range or in Comma separated list") @click.option('--no_restart_dhcp_relay', is_flag=True, type=click.BOOL, required=False, default=False, help="If no_restart_dhcp_relay is True, do not restart dhcp_relay while del vlan and \ require dhcpv6 relay of this is empty") @clicommon.pass_db -def del_vlan(db, vid, no_restart_dhcp_relay): +def del_vlan(db, vid, multiple, no_restart_dhcp_relay): """Delete VLAN""" log.log_info("'vlan del {}' executing...".format(vid)) @@ -227,7 +227,7 @@ def vlan_member(): pass @vlan_member.command('add') -@click.argument('vid', metavar='', required=True, type=int) +@click.argument('vid', metavar='', required=True) @click.argument('port', metavar='port', required=True) @click.option('-u', '--untagged', is_flag=True, help="Untagged status") @click.option('-m', '--multiple', is_flag=True, help="Add Multiple Vlan(s) in Range or in Comma separated list") @@ -332,7 +332,7 @@ def add_vlan_member(db, vid, port, untagged, multiple, except_flag): ctx.fail("{} invalid or does not exist, or {} invalid or does not exist".format(vlan, port)) @vlan_member.command('del') -@click.argument('vid', metavar='', required=True, type=int) +@click.argument('vid', metavar='', required=True) @click.argument('port', metavar='', required=True) @click.option('-m', '--multiple', is_flag=True, help="Add Multiple Vlan(s) in Range or in Comma separated list") @click.option('-e', '--except_flag', is_flag=True, help="Skips the given vlans and adds all other existing vlans") diff --git a/scripts/db_migrator.py b/scripts/db_migrator.py index 190148f91a..b07da88355 100755 --- a/scripts/db_migrator.py +++ b/scripts/db_migrator.py @@ -1141,9 +1141,18 @@ def version_4_0_3(self): Version 4_0_3. """ log.log_info('Handling version_4_0_3') + self.migrate_config_db_switchport_mode() + self.set_version('version_4_0_4') + return 'version_4_0_4' - self.set_version('version_202305_01') - return 'version_202305_01' + def version_4_0_4(self): + """ + Version 4_0_4. + """ + log.log_info('Handling version_4_0_4') + + self.set_version('version_202405_01') + return 'version_202405_01' def version_202305_01(self): """ diff --git a/tests/db_migrator_input/config_db/port-an-expected.json b/tests/db_migrator_input/config_db/port-an-expected.json index 14bdc415f4..45e13ef248 100644 --- a/tests/db_migrator_input/config_db/port-an-expected.json +++ b/tests/db_migrator_input/config_db/port-an-expected.json @@ -38,6 +38,6 @@ "fec": "none" }, "VERSIONS|DATABASE": { - "VERSION": "version_3_0_1" + "VERSION": "version_3_0_2" } } diff --git a/tests/db_migrator_input/config_db/port-an-input.json b/tests/db_migrator_input/config_db/port-an-input.json index 6cda388135..4209badb4f 100644 --- a/tests/db_migrator_input/config_db/port-an-input.json +++ b/tests/db_migrator_input/config_db/port-an-input.json @@ -34,6 +34,6 @@ "fec": "none" }, "VERSIONS|DATABASE": { - "VERSION": "version_3_0_0" + "VERSION": "version_3_0_1" } } \ No newline at end of file diff --git a/tests/db_migrator_input/config_db/switchport-expected.json b/tests/db_migrator_input/config_db/switchport-expected.json index 8242b6470b..cc8d7be694 100644 --- a/tests/db_migrator_input/config_db/switchport-expected.json +++ b/tests/db_migrator_input/config_db/switchport-expected.json @@ -31,7 +31,7 @@ "alias": "fortyGigE0/12", "index": "3", "lanes": "37,38,39,40", - "mode": "trunk", + "mode": "access", "mtu": "9100", "speed": "40000" }, @@ -139,6 +139,6 @@ }, "VERSIONS|DATABASE": { - "VERSION": "version_4_0_1" + "VERSION": "version_4_0_4" } } \ No newline at end of file diff --git a/tests/db_migrator_input/config_db/switchport-input.json b/tests/db_migrator_input/config_db/switchport-input.json index 9613f29e75..a710e0584c 100644 --- a/tests/db_migrator_input/config_db/switchport-input.json +++ b/tests/db_migrator_input/config_db/switchport-input.json @@ -133,6 +133,6 @@ }, "VERSIONS|DATABASE": { - "VERSION": "version_4_0_0" + "VERSION": "version_4_0_3" } } \ No newline at end of file diff --git a/tests/interfaces_test.py b/tests/interfaces_test.py index 8f7d7301ef..717dcbb4c9 100644 --- a/tests/interfaces_test.py +++ b/tests/interfaces_test.py @@ -160,8 +160,8 @@ Ethernet40 routed Ethernet44 routed Ethernet48 routed -Ethernet52 access -Ethernet56 access +Ethernet52 routed +Ethernet56 routed Ethernet60 routed Ethernet64 routed Ethernet68 routed @@ -199,8 +199,8 @@ Ethernet40 routed Ethernet44 routed Ethernet48 routed -Ethernet52 access -Ethernet56 access +Ethernet52 routed +Ethernet56 routed Ethernet60 routed Ethernet64 routed Ethernet68 routed diff --git a/tests/mock_tables/config_db.json b/tests/mock_tables/config_db.json index 71fc64968b..0ef506c288 100644 --- a/tests/mock_tables/config_db.json +++ b/tests/mock_tables/config_db.json @@ -703,7 +703,8 @@ "members@": "Ethernet32", "min_links": "1", "tpid": "0x8100", - "mtu": "9100" + "mtu": "9100", + "mode": "trunk" }, "PORTCHANNEL|PortChannel0001": { "admin_status": "up", From 789cfd4bd52e0e1b08d5e638b1bfcebe0446b0bb Mon Sep 17 00:00:00 2001 From: Saba Akram <126749695+sabakram@users.noreply.github.com> Date: Mon, 8 Jan 2024 11:14:45 +0000 Subject: [PATCH 09/19] Fix for errors --- config/main.py | 7 ------- tests/db_migrator_input/config_db/switchport-expected.json | 2 +- tests/db_migrator_test.py | 4 ++-- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/config/main.py b/config/main.py index a618465d53..5e82fc6c2e 100644 --- a/config/main.py +++ b/config/main.py @@ -4537,13 +4537,6 @@ def add(ctx, interface_name, ip_addr, gw): if interface_name is None: ctx.fail("'interface_name' is None!") - # Add a validation to check this interface is not a member in vlan before - # changing it to a router port - vlan_member_table = config_db.get_table('VLAN_MEMBER') - if (interface_is_in_vlan(vlan_member_table, interface_name)): - click.echo("Interface {} is a member of vlan\nAborting!".format(interface_name)) - return - portchannel_member_table = config_db.get_table('PORTCHANNEL_MEMBER') if interface_is_in_portchannel(portchannel_member_table, interface_name): diff --git a/tests/db_migrator_input/config_db/switchport-expected.json b/tests/db_migrator_input/config_db/switchport-expected.json index cc8d7be694..69d8e51ea7 100644 --- a/tests/db_migrator_input/config_db/switchport-expected.json +++ b/tests/db_migrator_input/config_db/switchport-expected.json @@ -110,7 +110,7 @@ "fast_rate": "false", "lacp_key": "auto", "min_links": "1", - "mode": "trunk", + "mode": "access", "mtu": "9100" }, "PORTCHANNEL|PortChannel0002": { diff --git a/tests/db_migrator_test.py b/tests/db_migrator_test.py index d4cb50ff50..d246c01db4 100644 --- a/tests/db_migrator_test.py +++ b/tests/db_migrator_test.py @@ -302,7 +302,7 @@ def test_port_autoneg_migrator(self): dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'config_db', 'port-an-expected') expected_db = Db() - advance_version_for_expected_database(dbmgtr.configDB, expected_db.cfgdb, 'version_3_0_1') + advance_version_for_expected_database(dbmgtr.configDB, expected_db.cfgdb, 'version_3_0_2') assert dbmgtr.configDB.get_table('PORT') == expected_db.cfgdb.get_table('PORT') assert dbmgtr.configDB.get_table('VERSIONS') == expected_db.cfgdb.get_table('VERSIONS') @@ -327,7 +327,7 @@ def test_switchport_mode_migrator(self): dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'config_db', 'switchport-expected') expected_db = Db() - advance_version_for_expected_database(dbmgtr.configDB, expected_db.cfgdb, 'version_4_0_1') + advance_version_for_expected_database(dbmgtr.configDB, expected_db.cfgdb, 'version_4_0_4') assert dbmgtr.configDB.get_table('PORT') == expected_db.cfgdb.get_table('PORT') assert dbmgtr.configDB.get_table('PORTCHANNEL') == expected_db.cfgdb.get_table('PORTCHANNEL') From 7bc67d779c1ab974e77132e8914f7435747ce196 Mon Sep 17 00:00:00 2001 From: Saba Akram <126749695+sabakram@users.noreply.github.com> Date: Mon, 8 Jan 2024 11:38:34 +0000 Subject: [PATCH 10/19] Fix for port version --- tests/db_migrator_input/config_db/port-an-expected.json | 2 +- tests/db_migrator_input/config_db/port-an-input.json | 2 +- tests/db_migrator_test.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/db_migrator_input/config_db/port-an-expected.json b/tests/db_migrator_input/config_db/port-an-expected.json index 45e13ef248..14bdc415f4 100644 --- a/tests/db_migrator_input/config_db/port-an-expected.json +++ b/tests/db_migrator_input/config_db/port-an-expected.json @@ -38,6 +38,6 @@ "fec": "none" }, "VERSIONS|DATABASE": { - "VERSION": "version_3_0_2" + "VERSION": "version_3_0_1" } } diff --git a/tests/db_migrator_input/config_db/port-an-input.json b/tests/db_migrator_input/config_db/port-an-input.json index 4209badb4f..6cda388135 100644 --- a/tests/db_migrator_input/config_db/port-an-input.json +++ b/tests/db_migrator_input/config_db/port-an-input.json @@ -34,6 +34,6 @@ "fec": "none" }, "VERSIONS|DATABASE": { - "VERSION": "version_3_0_1" + "VERSION": "version_3_0_0" } } \ No newline at end of file diff --git a/tests/db_migrator_test.py b/tests/db_migrator_test.py index d246c01db4..440b8acde6 100644 --- a/tests/db_migrator_test.py +++ b/tests/db_migrator_test.py @@ -302,7 +302,7 @@ def test_port_autoneg_migrator(self): dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'config_db', 'port-an-expected') expected_db = Db() - advance_version_for_expected_database(dbmgtr.configDB, expected_db.cfgdb, 'version_3_0_2') + advance_version_for_expected_database(dbmgtr.configDB, expected_db.cfgdb, 'version_3_0_1') assert dbmgtr.configDB.get_table('PORT') == expected_db.cfgdb.get_table('PORT') assert dbmgtr.configDB.get_table('VERSIONS') == expected_db.cfgdb.get_table('VERSIONS') From afa14764163d09a35c4185db326667af16abe368 Mon Sep 17 00:00:00 2001 From: Saba Akram <126749695+sabakram@users.noreply.github.com> Date: Mon, 8 Jan 2024 12:14:32 +0000 Subject: [PATCH 11/19] Fix for DB migrator versions --- scripts/db_migrator.py | 13 ++----------- .../config_db/switchport-expected.json | 2 +- .../config_db/switchport-input.json | 2 +- tests/db_migrator_test.py | 2 +- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/scripts/db_migrator.py b/scripts/db_migrator.py index b07da88355..1b2b7f23de 100755 --- a/scripts/db_migrator.py +++ b/scripts/db_migrator.py @@ -1141,18 +1141,9 @@ def version_4_0_3(self): Version 4_0_3. """ log.log_info('Handling version_4_0_3') - self.migrate_config_db_switchport_mode() - self.set_version('version_4_0_4') - return 'version_4_0_4' - - def version_4_0_4(self): - """ - Version 4_0_4. - """ - log.log_info('Handling version_4_0_4') - self.set_version('version_202405_01') - return 'version_202405_01' + self.set_version('version_202305_01') + return 'version_202305_01' def version_202305_01(self): """ diff --git a/tests/db_migrator_input/config_db/switchport-expected.json b/tests/db_migrator_input/config_db/switchport-expected.json index 69d8e51ea7..0f49b5edf3 100644 --- a/tests/db_migrator_input/config_db/switchport-expected.json +++ b/tests/db_migrator_input/config_db/switchport-expected.json @@ -139,6 +139,6 @@ }, "VERSIONS|DATABASE": { - "VERSION": "version_4_0_4" + "VERSION": "version_4_0_1" } } \ No newline at end of file diff --git a/tests/db_migrator_input/config_db/switchport-input.json b/tests/db_migrator_input/config_db/switchport-input.json index a710e0584c..9613f29e75 100644 --- a/tests/db_migrator_input/config_db/switchport-input.json +++ b/tests/db_migrator_input/config_db/switchport-input.json @@ -133,6 +133,6 @@ }, "VERSIONS|DATABASE": { - "VERSION": "version_4_0_3" + "VERSION": "version_4_0_0" } } \ No newline at end of file diff --git a/tests/db_migrator_test.py b/tests/db_migrator_test.py index 440b8acde6..d4cb50ff50 100644 --- a/tests/db_migrator_test.py +++ b/tests/db_migrator_test.py @@ -327,7 +327,7 @@ def test_switchport_mode_migrator(self): dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'config_db', 'switchport-expected') expected_db = Db() - advance_version_for_expected_database(dbmgtr.configDB, expected_db.cfgdb, 'version_4_0_4') + advance_version_for_expected_database(dbmgtr.configDB, expected_db.cfgdb, 'version_4_0_1') assert dbmgtr.configDB.get_table('PORT') == expected_db.cfgdb.get_table('PORT') assert dbmgtr.configDB.get_table('PORTCHANNEL') == expected_db.cfgdb.get_table('PORTCHANNEL') From 4529899b2a6ba352f89c69a8f01e8cec1ca81490 Mon Sep 17 00:00:00 2001 From: Saba Akram <126749695+sabakram@users.noreply.github.com> Date: Tue, 9 Jan 2024 10:05:59 +0000 Subject: [PATCH 12/19] Fix for db migrator version function --- scripts/db_migrator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/db_migrator.py b/scripts/db_migrator.py index 1b2b7f23de..f54940f50f 100755 --- a/scripts/db_migrator.py +++ b/scripts/db_migrator.py @@ -990,6 +990,7 @@ def version_3_0_0(self): """ log.log_info('Handling version_3_0_0') self.migrate_config_db_port_table_for_auto_neg() + self.migrate_config_db_switchport_mode() self.set_version('version_3_0_1') return 'version_3_0_1' From b469f1b3c2e9d1759e9c2a3cea41d9bfc2945f8a Mon Sep 17 00:00:00 2001 From: Saba Akram <126749695+sabakram@users.noreply.github.com> Date: Tue, 9 Jan 2024 10:40:43 +0000 Subject: [PATCH 13/19] Fixing versions --- scripts/db_migrator.py | 2 ++ tests/db_migrator_input/config_db/switchport-expected.json | 2 +- tests/db_migrator_input/config_db/switchport-input.json | 2 +- tests/db_migrator_test.py | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/db_migrator.py b/scripts/db_migrator.py index f54940f50f..40faa2ddfb 100755 --- a/scripts/db_migrator.py +++ b/scripts/db_migrator.py @@ -1006,7 +1006,9 @@ def version_3_0_1(self): for name, data in portchannel_table.items(): data['lacp_key'] = 'auto' self.configDB.set_entry('PORTCHANNEL', name, data) + self.migrate_config_db_switchport_mode() self.set_version('version_3_0_2') + return 'version_3_0_2' def version_3_0_2(self): diff --git a/tests/db_migrator_input/config_db/switchport-expected.json b/tests/db_migrator_input/config_db/switchport-expected.json index 0f49b5edf3..812abbd58f 100644 --- a/tests/db_migrator_input/config_db/switchport-expected.json +++ b/tests/db_migrator_input/config_db/switchport-expected.json @@ -139,6 +139,6 @@ }, "VERSIONS|DATABASE": { - "VERSION": "version_4_0_1" + "VERSION": "version_3_0_1" } } \ No newline at end of file diff --git a/tests/db_migrator_input/config_db/switchport-input.json b/tests/db_migrator_input/config_db/switchport-input.json index 9613f29e75..c1ad306ce4 100644 --- a/tests/db_migrator_input/config_db/switchport-input.json +++ b/tests/db_migrator_input/config_db/switchport-input.json @@ -133,6 +133,6 @@ }, "VERSIONS|DATABASE": { - "VERSION": "version_4_0_0" + "VERSION": "version_3_0_0" } } \ No newline at end of file diff --git a/tests/db_migrator_test.py b/tests/db_migrator_test.py index d4cb50ff50..bf7f4f39af 100644 --- a/tests/db_migrator_test.py +++ b/tests/db_migrator_test.py @@ -327,7 +327,7 @@ def test_switchport_mode_migrator(self): dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'config_db', 'switchport-expected') expected_db = Db() - advance_version_for_expected_database(dbmgtr.configDB, expected_db.cfgdb, 'version_4_0_1') + advance_version_for_expected_database(dbmgtr.configDB, expected_db.cfgdb, 'version_3_0_1') assert dbmgtr.configDB.get_table('PORT') == expected_db.cfgdb.get_table('PORT') assert dbmgtr.configDB.get_table('PORTCHANNEL') == expected_db.cfgdb.get_table('PORTCHANNEL') From 2a7bd65341c0e630b7f2247761601699dbcedae9 Mon Sep 17 00:00:00 2001 From: Saba Akram <126749695+sabakram@users.noreply.github.com> Date: Tue, 9 Jan 2024 11:08:10 +0000 Subject: [PATCH 14/19] Fix for cli.py --- utilities_common/cli.py | 251 ++++++++++++++++++++-------------------- 1 file changed, 126 insertions(+), 125 deletions(-) diff --git a/utilities_common/cli.py b/utilities_common/cli.py index aead848052..af3ca84903 100644 --- a/utilities_common/cli.py +++ b/utilities_common/cli.py @@ -188,6 +188,88 @@ def alias_to_name(self, interface_alias): # Lazy global class instance for SONiC interface name to alias conversion iface_alias_converter = lazy_object_proxy.Proxy(lambda: InterfaceAliasConverter()) +def get_interface_naming_mode(): + mode = os.getenv('SONIC_CLI_IFACE_MODE') + if mode is None: + mode = "default" + return mode + +def is_ipaddress(val): + """ Validate if an entry is a valid IP """ + import netaddr + if not val: + return False + try: + netaddr.IPAddress(str(val)) + except netaddr.core.AddrFormatError: + return False + return True + +def ipaddress_type(val): + """ Return the IP address type """ + if not val: + return None + + try: + ip_version = netaddr.IPAddress(str(val)) + except netaddr.core.AddrFormatError: + return None + + return ip_version.version + +def is_ip_prefix_in_key(key): + ''' + Function to check if IP address is present in the key. If it + is present, then the key would be a tuple or else, it shall be + be string + ''' + return (isinstance(key, tuple)) + +def is_valid_port(config_db, port): + """Check if port is in PORT table""" + + port_table = config_db.get_table('PORT') + if port in port_table: + return True + + return False + +def is_valid_portchannel(config_db, port): + """Check if port is in PORT_CHANNEL table""" + + pc_table = config_db.get_table('PORTCHANNEL') + if port in pc_table: + return True + + return False + +def is_vlanid_in_range(vid): + """Check if vlan id is valid or not""" + + if vid >= 1 and vid <= 4094: + return True + + return False + +def check_if_vlanid_exist(config_db, vlan, table_name='VLAN'): + """Check if vlan id exits in the config db or ot""" + + if len(config_db.get_entry(table_name, vlan)) != 0: + return True + + return False + +def is_port_vlan_member(config_db, port, vlan): + """Check if port is a member of vlan""" + + vlan_ports_data = config_db.get_table('VLAN_MEMBER') + for key in vlan_ports_data: + if key[0] == vlan and key[1] == port: + return True + + return False + + def vlan_range_list(ctx, vid_range: str) -> list: vid1, vid2 = map(int, vid_range.split("-")) @@ -292,9 +374,49 @@ def interface_is_tagged_member(db, interface_name): return True return False - def get_vlan_id(vlan): - vlan_prefix, vid = vlan.split('Vlan') - return vid +def interface_is_in_vlan(vlan_member_table, interface_name): + """ Check if an interface is in a vlan """ + for _,intf in vlan_member_table: + if intf == interface_name: + return True + + return False + +def is_valid_vlan_interface(config_db, interface): + """ Check an interface is a valid VLAN interface """ + return interface in config_db.get_table("VLAN_INTERFACE") + +def interface_is_in_portchannel(portchannel_member_table, interface_name): + """ Check if an interface is part of portchannel """ + for _,intf in portchannel_member_table: + if intf == interface_name: + return True + + return False + +def is_port_router_interface(config_db, port): + """Check if port is a router interface""" + + interface_table = config_db.get_table('INTERFACE') + for intf in interface_table: + if port == intf: + return True + + return False + +def is_pc_router_interface(config_db, pc): + """Check if portchannel is a router interface""" + + pc_interface_table = config_db.get_table('PORTCHANNEL_INTERFACE') + for intf in pc_interface_table: + if pc == intf: + return True + + return False + +def get_vlan_id(vlan): + vlan_prefix, vid = vlan.split('Vlan') + return vid def get_interface_name_for_display(db ,interface): interface_naming_mode = get_interface_naming_mode() @@ -341,127 +463,6 @@ def get_interface_switchport_mode(db, interface): switchport_mode = portchannel['mode'] return switchport_mode -def get_interface_naming_mode(): - mode = os.getenv('SONIC_CLI_IFACE_MODE') - if mode is None: - mode = "default" - return mode - -def is_ipaddress(val): - """ Validate if an entry is a valid IP """ - import netaddr - if not val: - return False - try: - netaddr.IPAddress(str(val)) - except netaddr.core.AddrFormatError: - return False - return True - -def ipaddress_type(val): - """ Return the IP address type """ - if not val: - return None - - try: - ip_version = netaddr.IPAddress(str(val)) - except netaddr.core.AddrFormatError: - return None - - return ip_version.version - -def is_ip_prefix_in_key(key): - ''' - Function to check if IP address is present in the key. If it - is present, then the key would be a tuple or else, it shall be - be string - ''' - return (isinstance(key, tuple)) - -def is_valid_port(config_db, port): - """Check if port is in PORT table""" - - port_table = config_db.get_table('PORT') - if port in port_table: - return True - - return False - -def is_valid_portchannel(config_db, port): - """Check if port is in PORT_CHANNEL table""" - - pc_table = config_db.get_table('PORTCHANNEL') - if port in pc_table: - return True - - return False - -def is_vlanid_in_range(vid): - """Check if vlan id is valid or not""" - - if vid >= 1 and vid <= 4094: - return True - - return False - -def check_if_vlanid_exist(config_db, vlan, table_name='VLAN'): - """Check if vlan id exits in the config db or ot""" - - if len(config_db.get_entry(table_name, vlan)) != 0: - return True - - return False - -def is_port_vlan_member(config_db, port, vlan): - """Check if port is a member of vlan""" - - vlan_ports_data = config_db.get_table('VLAN_MEMBER') - for key in vlan_ports_data: - if key[0] == vlan and key[1] == port: - return True - - return False - -def interface_is_in_vlan(vlan_member_table, interface_name): - """ Check if an interface is in a vlan """ - for _,intf in vlan_member_table: - if intf == interface_name: - return True - - return False - -def is_valid_vlan_interface(config_db, interface): - """ Check an interface is a valid VLAN interface """ - return interface in config_db.get_table("VLAN_INTERFACE") - -def interface_is_in_portchannel(portchannel_member_table, interface_name): - """ Check if an interface is part of portchannel """ - for _,intf in portchannel_member_table: - if intf == interface_name: - return True - - return False - -def is_port_router_interface(config_db, port): - """Check if port is a router interface""" - - interface_table = config_db.get_table('INTERFACE') - for intf in interface_table: - if port == intf: - return True - - return False - -def is_pc_router_interface(config_db, pc): - """Check if portchannel is a router interface""" - - pc_interface_table = config_db.get_table('PORTCHANNEL_INTERFACE') - for intf in pc_interface_table: - if pc == intf: - return True - - return False - def is_port_mirror_dst_port(config_db, port): """Check if port is already configured as mirror destination port """ mirror_table = config_db.get_table('MIRROR_SESSION') @@ -863,4 +864,4 @@ def remove(self): def remove_all(self): """ Remove the content of the cache for all users """ - shutil.rmtree(self.cache_directory_app) + shutil.rmtree(self.cache_directory_app) \ No newline at end of file From a87039d28ea89e0fab9896e8b9f652c30189cfc5 Mon Sep 17 00:00:00 2001 From: Saba Akram <126749695+sabakram@users.noreply.github.com> Date: Tue, 9 Jan 2024 11:29:02 +0000 Subject: [PATCH 15/19] Fix for vlan_test.py --- tests/vlan_test.py | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/tests/vlan_test.py b/tests/vlan_test.py index 8a381ca8fe..967a650127 100644 --- a/tests/vlan_test.py +++ b/tests/vlan_test.py @@ -674,13 +674,13 @@ def test_config_add_del_vlan_and_vlan_member(self, mock_restart_dhcp_relay_servi print(result.output) assert result.exit_code == 0 - # add Ethernet20 to vlan 1001 + # add Ethernet20 to vlan 1001 but Ethernet20 is in routed mode will give error result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], ["1001", "Ethernet20", "--untagged"], obj=db) print(result.exit_code) print(result.output) traceback.print_tb(result.exc_info[2]) - assert result.exit_code == 0 + assert result.exit_code != 0 assert "Ethernet20 is in routed mode!\nUse switchport mode command to change port mode" in result.output # configure Ethernet20 from routed to access mode @@ -690,6 +690,14 @@ def test_config_add_del_vlan_and_vlan_member(self, mock_restart_dhcp_relay_servi assert result.exit_code == 0 assert "Ethernet20 switched from routed to access mode" in result.output + # add Ethernet20 to vlan 1001 + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["1001", "Ethernet20", "--untagged"], obj=db) + print(result.exit_code) + print(result.output) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code == 0 + assert "Ethernet20 is in routed mode!\nUse switchport mode command to change port mode" in result.output # show output result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) @@ -728,15 +736,29 @@ def test_config_add_del_vlan_and_vlan_member_in_alias_mode(self, mock_restart_dh print(result.output) assert result.exit_code == 0 - # add etp6 to vlan 1001 + # add etp6 to vlan 1001 but etp6 is in routed mode will give error result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], ["1001", "etp6", "--untagged"], obj=db) print(result.exit_code) print(result.output) traceback.print_tb(result.exc_info[2]) - assert result.exit_code == 0 + assert result.exit_code != 0 + assert "Ethernet20 is in routed mode!\nUse switchport mode command to change port mode" in result.output + # configure etp6 from routed to access mode + result = runner.invoke(config.config.commands["switchport"].commands["mode"],["access", "etp6"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert "Ethernet20 switched from routed to access mode" in result.output + # add etp6 to vlan 1001 + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["1001", "etp6", "--untagged"], obj=db) + print(result.exit_code) + print(result.output) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code == 0 # show output result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) @@ -1230,7 +1252,7 @@ def test_config_set_router_port_on_member_interface(self): result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], ["Ethernet4", "10.10.10.1/24"], obj=obj) print(result.exit_code, result.output) - assert result.exit_code == 0 + assert result.exit_code != 0 assert 'Interface Ethernet4 is not in routed mode!' in result.output def test_config_vlan_add_member_of_portchannel(self): From ad3a193696fb5a8f38d1c8b5bc77c1f1e0768ee2 Mon Sep 17 00:00:00 2001 From: Saba Akram <126749695+sabakram@users.noreply.github.com> Date: Wed, 10 Jan 2024 08:17:38 +0000 Subject: [PATCH 16/19] Fix for failures --- config/vlan.py | 60 ++++++++++++++++++++++++++-------------------- tests/vlan_test.py | 3 +-- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/config/vlan.py b/config/vlan.py index 1244b4f5ea..2bd5734836 100644 --- a/config/vlan.py +++ b/config/vlan.py @@ -56,6 +56,10 @@ def add_vlan(db, vid, multiple): # loop will execute till an exception occurs for vid in vid_list: + if not clicommon.is_vlanid_in_range(vid): + ctx.fail("Invalid VLAN ID {} (2-4094)".format(vid)) + + #Multiple VLANs need to be referenced vlan = 'Vlan{}'.format(vid) # default vlan checker @@ -65,9 +69,6 @@ def add_vlan(db, vid, multiple): log.log_info("'vlan add {}' executing...".format(vid)) - if not clicommon.is_vlanid_in_range(vid): - ctx.fail("Invalid VLAN ID {} (2-4094)".format(vid)) - # TODO: MISSING CONSTRAINT IN YANG MODEL if clicommon.check_if_vlanid_exist(db.cfgdb, vlan): log.log_info("{} already exists".format(vlan)) @@ -76,8 +77,8 @@ def add_vlan(db, vid, multiple): if clicommon.check_if_vlanid_exist(db.cfgdb, vlan, "DHCP_RELAY"): ctx.fail("DHCPv6 relay config for {} already exists".format(vlan)) - # set dhcpv4_relay table - set_dhcp_relay_table('VLAN', config_db, vlan, {'vlanid': str(vid)}) + # set dhcpv4_relay table + set_dhcp_relay_table('VLAN', config_db, vlan, {'vlanid': str(vid)}) def is_dhcpv6_relay_config_exist(db, vlan_name): @@ -107,8 +108,6 @@ def delete_db_entry(entry_name, db_connector, db_name): def del_vlan(db, vid, multiple, no_restart_dhcp_relay): """Delete VLAN""" - log.log_info("'vlan del {}' executing...".format(vid)) - ctx = click.get_current_context() vid_list = [] @@ -119,18 +118,23 @@ def del_vlan(db, vid, multiple, no_restart_dhcp_relay): if not vid.isdigit(): ctx.fail("{} is not integer".format(vid)) vid_list.append(int(vid)) - - if no_restart_dhcp_relay: - if is_dhcpv6_relay_config_exist(db, vlan): - ctx.fail("Can't delete {} because related DHCPv6 Relay config is exist".format(vlan)) - + config_db = ValidatedConfigDBConnector(db.cfgdb) if ADHOC_VALIDATION: for vid in vid_list: log.log_info("'vlan del {}' executing...".format(vid)) + if not clicommon.is_vlanid_in_range(vid): ctx.fail("Invalid VLAN ID {} (2-4094)".format(vid)) + + #Multiple VLANs needs to be referenced vlan = 'Vlan{}'.format(vid) + + #Multiple VLANs needs to be checked + if no_restart_dhcp_relay: + if is_dhcpv6_relay_config_exist(db, vlan): + ctx.fail("Can't delete {} because related DHCPv6 Relay config is exist".format(vlan)) + if clicommon.check_if_vlanid_exist(db.cfgdb, vlan) == False: log.log_info("{} does not exist".format(vlan)) ctx.fail("{} does not exist, Aborting!!!".format(vlan)) @@ -151,18 +155,18 @@ def del_vlan(db, vid, multiple, no_restart_dhcp_relay): if vxmap_data['vlan'] == 'Vlan{}'.format(vid): ctx.fail("vlan: {} can not be removed. First remove vxlan mapping '{}' assigned to VLAN".format(vid, '|'.join(vxmap_key))) - # set dhcpv4_relay table - set_dhcp_relay_table('VLAN', config_db, vlan, None) + # set dhcpv4_relay table + set_dhcp_relay_table('VLAN', config_db, vlan, None) - if not no_restart_dhcp_relay and is_dhcpv6_relay_config_exist(db, vlan): - # set dhcpv6_relay table - set_dhcp_relay_table('DHCP_RELAY', config_db, vlan, None) - # We need to restart dhcp_relay service after dhcpv6_relay config change - if is_dhcp_relay_running(): - dhcp_relay_util.handle_restart_dhcp_relay_service() + if not no_restart_dhcp_relay and is_dhcpv6_relay_config_exist(db, vlan): + # set dhcpv6_relay table + set_dhcp_relay_table('DHCP_RELAY', config_db, vlan, None) + # We need to restart dhcp_relay service after dhcpv6_relay config change + if is_dhcp_relay_running(): + dhcp_relay_util.handle_restart_dhcp_relay_service() - delete_db_entry("DHCPv6_COUNTER_TABLE|{}".format(vlan), db.db, db.db.STATE_DB) - delete_db_entry("DHCP_COUNTER_TABLE|{}".format(vlan), db.db, db.db.STATE_DB) + delete_db_entry("DHCPv6_COUNTER_TABLE|{}".format(vlan), db.db, db.db.STATE_DB) + delete_db_entry("DHCP_COUNTER_TABLE|{}".format(vlan), db.db, db.db.STATE_DB) vlans = db.cfgdb.get_keys('VLAN') if not vlans: @@ -238,8 +242,6 @@ def add_vlan_member(db, vid, port, untagged, multiple, except_flag): ctx = click.get_current_context() - log.log_info("'vlan member add {} {}' executing...".format(vid, port)) - # parser will parse the vid input if there are syntax errors it will throw error vid_list = clicommon.vlan_member_input_parser(ctx, "add", db, except_flag, multiple, vid, port) @@ -253,6 +255,8 @@ def add_vlan_member(db, vid, port, untagged, multiple, except_flag): if ADHOC_VALIDATION: for vid in vid_list: + vlan = 'Vlan{}'.format(vid) + # default vlan checker if vid == 1: ctx.fail("{} is default VLAN".format(vlan)) @@ -261,7 +265,6 @@ def add_vlan_member(db, vid, port, untagged, multiple, except_flag): if not clicommon.is_vlanid_in_range(vid): ctx.fail("Invalid VLAN ID {} (2-4094)".format(vid)) - vlan = 'Vlan{}'.format(vid) if clicommon.check_if_vlanid_exist(db.cfgdb, vlan) == False: log.log_info("{} does not exist".format(vlan)) @@ -290,6 +293,10 @@ def add_vlan_member(db, vid, port, untagged, multiple, except_flag): is_port = False else: ctx.fail("{} does not exist".format(port)) + + if (is_port and clicommon.is_port_router_interface(db.cfgdb, port)) or \ + (not is_port and clicommon.is_pc_router_interface(db.cfgdb, port)): # TODO: MISSING CONSTRAINT IN YANG MODEL + ctx.fail("{} is a router interface!".format(port)) portchannel_member_table = db.cfgdb.get_table('PORTCHANNEL_MEMBER') @@ -356,8 +363,9 @@ def del_vlan_member(db, vid, port, multiple, except_flag): ctx.fail("Invalid VLAN ID {} (2-4094)".format(vid)) vlan = 'Vlan{}'.format(vid) + if clicommon.check_if_vlanid_exist(db.cfgdb, vlan) == False: - log.log_info("{} does not exist, Aborting!!!".format(vlan)) + log.log_info("{} does not exist".format(vlan)) ctx.fail("{} does not exist, Aborting!!!".format(vlan)) if clicommon.get_interface_naming_mode() == "alias": # TODO: MISSING CONSTRAINT IN YANG MODEL diff --git a/tests/vlan_test.py b/tests/vlan_test.py index 967a650127..f458f7461a 100644 --- a/tests/vlan_test.py +++ b/tests/vlan_test.py @@ -697,7 +697,6 @@ def test_config_add_del_vlan_and_vlan_member(self, mock_restart_dhcp_relay_servi print(result.output) traceback.print_tb(result.exc_info[2]) assert result.exit_code == 0 - assert "Ethernet20 is in routed mode!\nUse switchport mode command to change port mode" in result.output # show output result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) @@ -793,7 +792,7 @@ def test_config_add_del_multiple_vlan_and_vlan_member(self,mock_restart_dhcp_rel runner = CliRunner() db = Db() - # add vlan 1001 + # add vlan 1001,1002,1003 result = runner.invoke(config.config.commands["vlan"].commands["add"], ["1001,1002,1003","--multiple"], obj=db) print(result.exit_code) print(result.output) From 7593c2d283358e4de5d4da2a9b44a6e53e0e0f90 Mon Sep 17 00:00:00 2001 From: Saba Akram <126749695+sabakram@users.noreply.github.com> Date: Wed, 10 Jan 2024 08:29:47 +0000 Subject: [PATCH 17/19] Fix for unexpected characters --- config/vlan.py | 2 +- tests/vlan_test.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config/vlan.py b/config/vlan.py index 2bd5734836..0404c080f6 100644 --- a/config/vlan.py +++ b/config/vlan.py @@ -294,7 +294,7 @@ def add_vlan_member(db, vid, port, untagged, multiple, except_flag): else: ctx.fail("{} does not exist".format(port)) - if (is_port and clicommon.is_port_router_interface(db.cfgdb, port)) or \ + if (is_port and clicommon.is_port_router_interface(db.cfgdb, port)) or \ (not is_port and clicommon.is_pc_router_interface(db.cfgdb, port)): # TODO: MISSING CONSTRAINT IN YANG MODEL ctx.fail("{} is a router interface!".format(port)) diff --git a/tests/vlan_test.py b/tests/vlan_test.py index f458f7461a..6b72598e51 100644 --- a/tests/vlan_test.py +++ b/tests/vlan_test.py @@ -807,7 +807,7 @@ def test_config_add_del_multiple_vlan_and_vlan_member(self,mock_restart_dhcp_rel assert result.exit_code != 0 assert "Ethernet20 is in routed mode!\nUse switchport mode command to change port mode" in result.output - # configure Ethernet20 from routed to access mode + # configure Ethernet20 from routed to trunk mode result = runner.invoke(config.config.commands["switchport"].commands["mode"],["trunk", "Ethernet20"], obj=db) print(result.exit_code) print(result.output) @@ -851,7 +851,7 @@ def test_config_add_del_add_vlans_and_add_vlans_member_except_vlan(self, mock_re runner = CliRunner() db = Db() - # add vlan 1001 + # add vlan 1001,1002 result = runner.invoke(config.config.commands["vlan"].commands["add"], ["1001,1002","--multiple"], obj=db) print(result.exit_code) print(result.output) @@ -866,14 +866,14 @@ def test_config_add_del_add_vlans_and_add_vlans_member_except_vlan(self, mock_re assert result.exit_code != 0 assert "Ethernet20 is in routed mode!\nUse switchport mode command to change port mode" in result.output - # configure Ethernet20 from routed to access mode + # configure Ethernet20 from routed to trunk mode result = runner.invoke(config.config.commands["switchport"].commands["mode"],["trunk", "Ethernet20"], obj=db) print(result.exit_code) print(result.output) assert result.exit_code == 0 assert "Ethernet20 switched from routed to trunk mode" in result.output - # add Ethernet20 to vlan 1001 + # add Ethernet20 to vlan1001, vlan1002, vlan1003 multiple flag result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], ["1000,4000", "Ethernet20", "--multiple", "--except_flag"], obj=db) print(result.exit_code) @@ -907,7 +907,7 @@ def test_config_add_del_add_vlans_and_add_vlans_member_except_vlan(self, mock_re print(result.output) assert result.exit_code == 0 - # add del 1001 + # del 1001,1002 result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001-1002","--multiple"], obj=db) print(result.exit_code) print(result.output) From 9a3340d568899276726681df4693e7497c473d21 Mon Sep 17 00:00:00 2001 From: Saba Akram <126749695+sabakram@users.noreply.github.com> Date: Wed, 10 Jan 2024 08:56:50 +0000 Subject: [PATCH 18/19] Fixing error message --- config/vlan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/vlan.py b/config/vlan.py index 0404c080f6..121a854c32 100644 --- a/config/vlan.py +++ b/config/vlan.py @@ -296,7 +296,7 @@ def add_vlan_member(db, vid, port, untagged, multiple, except_flag): if (is_port and clicommon.is_port_router_interface(db.cfgdb, port)) or \ (not is_port and clicommon.is_pc_router_interface(db.cfgdb, port)): # TODO: MISSING CONSTRAINT IN YANG MODEL - ctx.fail("{} is a router interface!".format(port)) + ctx.fail("{} is in routed mode!\nUse switchport mode command to change port mode".format(port)) portchannel_member_table = db.cfgdb.get_table('PORTCHANNEL_MEMBER') From 5e9a8f023cf63edaca232187728d5b818a4fca8e Mon Sep 17 00:00:00 2001 From: Saba Akram <126749695+sabakram@users.noreply.github.com> Date: Wed, 10 Jan 2024 09:54:26 +0000 Subject: [PATCH 19/19] Fix for routed port --- tests/vlan_test.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/vlan_test.py b/tests/vlan_test.py index 6b72598e51..4d7ed0e947 100644 --- a/tests/vlan_test.py +++ b/tests/vlan_test.py @@ -1103,13 +1103,6 @@ def test_config_add_del_vlan_and_vlan_member_with_switchport_modes_and_change_mo print(result.output) assert result.exit_code == 0 - - # configure Ethernet64 to routed mode - result = runner.invoke(config.config.commands["switchport"].commands["mode"],["routed", "Ethernet64"], obj=db) - print(result.exit_code) - print(result.output) - assert result.exit_code == 0 - # add Ethernet64 to vlan 1001 but Ethernet64 is in routed mode will give error result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], ["1001", "Ethernet64"], obj=db)