diff --git a/config/main.py b/config/main.py index ea2a5154e3..ac7cd69c58 100644 --- a/config/main.py +++ b/config/main.py @@ -57,8 +57,10 @@ from .config_mgmt import ConfigMgmtDPB, ConfigMgmt from . import mclag from . import syslog +from . import switchport from . import dns + # mock masic APIs for unit test try: if os.environ["UTILITIES_UNIT_TESTING"] == "1" or os.environ["UTILITIES_UNIT_TESTING"] == "2": @@ -105,6 +107,7 @@ PORT_SPEED = "speed" PORT_TPID = "tpid" DEFAULT_TPID = "0x8100" +PORT_MODE = "switchport_mode" DOM_CONFIG_SUPPORTED_SUBPORTS = ['0', '1'] @@ -1231,6 +1234,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?') @@ -4639,19 +4645,38 @@ 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): 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 = None + if is_port: + interface_data = config_db.get_entry('PORT', interface_name) + else: + interface_data = config_db.get_entry('PORTCHANNEL', interface_name) + + if "mode" in interface_data: + interface_mode = interface_data["mode"] + + if interface_mode == "trunk" or interface_mode == "access": + click.echo("Interface {} is in {} mode and needs to be in routed mode!".format( + interface_name, interface_mode)) + 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..ca94a08444 --- /dev/null +++ b/config/switchport.py @@ -0,0 +1,135 @@ +import click +from .utils import log +import utilities_common.cli as clicommon + +# +# 'switchport' mode ('config switchport ...') +# + + +@click.group(cls=clicommon.AbbreviationGroup, name='switchport', invoke_without_command=False) +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(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: + 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 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 mode_exists_status: + if existing_mode == "routed": + # 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 existing_mode == type: + ctx.fail("{} is already in {} 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 to {} mode".format(port, 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: + 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 + + if mode_exists_status: + if 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 to {} mode".format(port, type)) diff --git a/config/vlan.py b/config/vlan.py index 7ace1d6d5f..98cc95757e 100644 --- a/config/vlan.py +++ b/config/vlan.py @@ -1,7 +1,6 @@ import click import utilities_common.cli as clicommon import utilities_common.dhcp_relay_util as dhcp_relay_util -from swsscommon.swsscommon import SonicV2Connector from jsonpatch import JsonPatchConflict from time import sleep @@ -15,6 +14,8 @@ # # 'vlan' group ('config vlan ...') # + + @click.group(cls=clicommon.AbbreviationGroup, name='vlan') def vlan(): """VLAN-related configuration tasks""" @@ -32,28 +33,51 @@ 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): +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: + + 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) + + # Defualt VLAN checker + if vid == 1: + ctx.fail("{} is default VLAN".format(vlan)) # TODO: MISSING CONSTRAINT IN YANG MODEL + + log.log_info("'vlan add {}' executing...".format(vid)) - 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)}) + 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)}) def is_dhcpv6_relay_config_exist(db, vlan_name): @@ -74,68 +98,91 @@ 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)) - ctx = click.get_current_context() - vlan = 'Vlan{}'.format(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)) + + 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)) 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)) + 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) is False: + ctx.fail("{} does not exist".format(vlan)) - 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)) - 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)] - 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)) - 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))) - 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) - # 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: docker_exec_cmd = ['docker', 'exec', '-i', 'swss'] - _, rc = clicommon.run_command(docker_exec_cmd + ['supervisorctl', 'status', 'ndppd'], ignore_error=True, return_cmd=True) + _, rc = clicommon.run_command( + docker_exec_cmd + ['supervisorctl', 'status', 'ndppd'], ignore_error=True, return_cmd=True) if rc == 0: click.echo("No VLANs remaining, stopping ndppd service") - clicommon.run_command(docker_exec_cmd + ['supervisorctl', 'stop', 'ndppd'], ignore_error=True, return_cmd=True) - clicommon.run_command(docker_exec_cmd + ['rm', '-f', '/etc/supervisor/conf.d/ndppd.conf'], ignore_error=True, return_cmd=True) + clicommon.run_command( + docker_exec_cmd + ['supervisorctl', 'stop', 'ndppd'], ignore_error=True, return_cmd=True) + clicommon.run_command( + docker_exec_cmd + ['rm', '-f', '/etc/supervisor/conf.d/ndppd.conf'], ignore_error=True, return_cmd=True) clicommon.run_command(docker_exec_cmd + ['supervisorctl', 'update'], return_cmd=True) + def restart_ndppd(): verify_swss_running_cmd = ['docker', 'container', 'inspect', '-f', '{{.State.Status}}', 'swss'] docker_exec_cmd = ['docker', 'exec', '-i', 'swss'] @@ -148,7 +195,8 @@ def restart_ndppd(): output, _ = clicommon.run_command(verify_swss_running_cmd, return_cmd=True) if output and output.strip() != "running": - click.echo(click.style('SWSS container is not running, changes will take effect the next time the SWSS container starts', fg='red'),) + click.echo(click.style('SWSS container is not running, ' + 'changes will take effect the next time the SWSS container starts', fg='red'),) return _, rc = clicommon.run_command(docker_exec_cmd + ndppd_status_cmd, ignore_error=True, return_cmd=True) @@ -162,6 +210,7 @@ def restart_ndppd(): sleep(3) clicommon.run_command(docker_exec_cmd + ndppd_restart_cmd, return_cmd=True) + @vlan.command('proxy_arp') @click.argument('vid', metavar='', required=True, type=int) @click.argument('mode', metavar='', required=True, type=click.Choice(["enabled", "disabled"])) @@ -184,102 +233,132 @@ def config_proxy_arp(db, vid, mode): # # 'member' group ('config vlan member ...') # + + @vlan.group(cls=clicommon.AbbreviationGroup, name='member') 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) +@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") +@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)) + for vid in vid_list: + vlan = 'Vlan{}'.format(vid) + # 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)) + if clicommon.check_if_vlanid_exist(db.cfgdb, vlan) is 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)) + 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)) + # 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) + existing_mode = None + if "mode" in port_data: + 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 + 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)) - 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)) - - 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)) @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") @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)) + 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)) - if clicommon.check_if_vlanid_exist(db.cfgdb, vlan) == False: - ctx.fail("{} does not exist".format(vlan)) + vlan = 'Vlan{}'.format(vid) - 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.check_if_vlanid_exist(db.cfgdb, vlan) is False: + ctx.fail("{} does not exist".format(vlan)) - 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)) + 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)) - 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)) + 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)) diff --git a/doc/Command-Reference.md b/doc/Command-Reference.md index e97922af65..4051ba9d81 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) @@ -4486,6 +4488,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 ``` @@ -4951,6 +4954,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 th +is command. + + + **show interfaces transceiver** This command is already explained [here](#Transceivers) @@ -10103,6 +10153,31 @@ 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 @@ -10887,6 +10962,22 @@ 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. @@ -10908,6 +10999,37 @@ 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 1 +05 + ``` + **config proxy_arp enabled/disabled** This command is used to enable or disable proxy ARP for a VLAN interface diff --git a/show/interfaces/__init__.py b/show/interfaces/__init__.py index a5a3734664..9287eb5af7 100644 --- a/show/interfaces/__init__.py +++ b/show/interfaces/__init__.py @@ -719,7 +719,7 @@ def autoneg_status(interfacename, namespace, display, verbose): cmd = ['intfutil', '-c', 'autoneg'] - #ignore the display option when interface name is passed + # ignore the display option when interface name is passed if interfacename is not None: interfacename = try_convert_interfacename_from_alias(ctx, interfacename) @@ -735,6 +735,8 @@ def autoneg_status(interfacename, namespace, display, verbose): # # link-training group (show interfaces link-training ...) # + + @interfaces.group(name='link-training', cls=clicommon.AliasedGroup) def link_training(): """Show interface link-training information""" @@ -752,7 +754,7 @@ def link_training_status(interfacename, namespace, display, verbose): cmd = ['intfutil', '-c', 'link_training'] - #ignore the display option when interface name is passed + # ignore the display option when interface name is passed if interfacename is not None: interfacename = try_convert_interfacename_from_alias(ctx, interfacename) @@ -785,7 +787,7 @@ def fec_status(interfacename, namespace, display, verbose): cmd = ['intfutil', '-c', 'fec'] - #ignore the display option when interface name is passed + # ignore the display option when interface name is passed if interfacename is not None: interfacename = try_convert_interfacename_from_alias(ctx, interfacename) @@ -797,3 +799,74 @@ 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/interfaces_test.py b/tests/interfaces_test.py index c3246ba026..6acdb505ac 100644 --- a/tests/interfaces_test.py +++ b/tests/interfaces_test.py @@ -5,9 +5,11 @@ from unittest import mock from utilities_common.intf_filter import parse_interface_in_filter +import config.main as config import show.main as show +from utilities_common.db import Db -show_interfaces_alias_output="""\ +show_interfaces_alias_output = """\ Name Alias ----------- ------- Ethernet0 etp1 @@ -44,13 +46,13 @@ Ethernet124 etp32 """ -show_interfaces_alias_Ethernet0_output="""\ +show_interfaces_alias_Ethernet0_output = """\ Name Alias --------- ------- Ethernet0 etp1 """ -show_interfaces_neighbor_expected_output="""\ +show_interfaces_neighbor_expected_output = """\ LocalPort Neighbor NeighborPort NeighborLoopback NeighborMgmt NeighborType ----------- ---------- -------------- ------------------ -------------- -------------- Ethernet112 ARISTA01T1 Ethernet1 None 10.250.0.51 LeafRouter @@ -59,7 +61,7 @@ Ethernet124 ARISTA04T1 Ethernet1 None 10.250.0.54 LeafRouter """ -show_interfaces_neighbor_expected_output_t1="""\ +show_interfaces_neighbor_expected_output_t1 = """\ LocalPort Neighbor NeighborPort NeighborLoopback NeighborMgmt NeighborType ----------- ---------- -------------- ------------------ -------------- -------------- Ethernet0 ARISTA01T2 Ethernet1 None 172.16.137.56 SpineRouter @@ -96,31 +98,31 @@ Ethernet124 ARISTA16T0 Ethernet1 None 172.16.137.79 ToRRouter """ -show_interfaces_neighbor_expected_output_Ethernet112="""\ +show_interfaces_neighbor_expected_output_Ethernet112 = """\ LocalPort Neighbor NeighborPort NeighborLoopback NeighborMgmt NeighborType ----------- ---------- -------------- ------------------ -------------- -------------- Ethernet112 ARISTA01T1 Ethernet1 None 10.250.0.51 LeafRouter """ -show_interfaces_neighbor_expected_output_t1_Ethernet0="""\ +show_interfaces_neighbor_expected_output_t1_Ethernet0 = """\ LocalPort Neighbor NeighborPort NeighborLoopback NeighborMgmt NeighborType ----------- ---------- -------------- ------------------ -------------- -------------- Ethernet0 ARISTA01T2 Ethernet1 None 172.16.137.56 SpineRouter """ -show_interfaces_neighbor_expected_output_etp29="""\ +show_interfaces_neighbor_expected_output_etp29 = """\ LocalPort Neighbor NeighborPort NeighborLoopback NeighborMgmt NeighborType ----------- ---------- -------------- ------------------ -------------- -------------- etp29 ARISTA01T1 Ethernet1 None 10.250.0.51 LeafRouter """ -show_interfaces_neighbor_expected_output_t1_Ethernet1_1="""\ +show_interfaces_neighbor_expected_output_t1_Ethernet1_1 = """\ LocalPort Neighbor NeighborPort NeighborLoopback NeighborMgmt NeighborType ----------- ---------- -------------- ------------------ -------------- -------------- Ethernet1/1 ARISTA01T2 Ethernet1 None 172.16.137.56 SpineRouter """ -show_interfaces_portchannel_output="""\ +show_interfaces_portchannel_output = """\ Flags: A - active, I - inactive, Up - up, Dw - Down, N/A - not available, S - selected, D - deselected, * - not synced No. Team Dev Protocol Ports @@ -132,7 +134,7 @@ 1001 PortChannel1001 N/A """ -show_interfaces_portchannel_in_alias_mode_output="""\ +show_interfaces_portchannel_in_alias_mode_output = """\ Flags: A - active, I - inactive, Up - up, Dw - Down, N/A - not available, S - selected, D - deselected, * - not synced No. Team Dev Protocol Ports @@ -144,6 +146,124 @@ 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 routed +Ethernet56 routed +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 routed +Ethernet56 routed +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 +""" + +show_interfaces_switchport_config_in_alias_mode_output = """\ +Interface Mode Untagged Tagged +--------------- ------ ---------- -------- +etp1 routed +etp2 trunk 1000 +etp3 routed 1000 +etp4 routed 1000 +etp5 trunk 1000 +etp6 routed +etp7 trunk 2000 +etp8 trunk 2000 +etp10 routed +etp11 routed +etp12 routed +etp13 routed +etp14 routed +etp15 routed +etp16 routed +etp17 routed +etp18 routed +etp19 routed +etp20 routed +etp21 routed +etp22 routed +etp23 routed +etp24 routed +etp25 routed +etp26 routed +etp27 routed +etp28 routed +etp30 routed +etp32 routed +PortChannel0001 routed +PortChannel0002 routed +PortChannel0003 routed +PortChannel0004 routed +PortChannel1001 trunk 4000 +""" + + class TestInterfaces(object): @classmethod def setup_class(cls): @@ -228,7 +348,8 @@ def test_show_interfaces_neighbor_expected_t1(self, setup_t1_topo): def test_show_interfaces_neighbor_expected_Ethernet112(self): runner = CliRunner() - result = runner.invoke(show.cli.commands["interfaces"].commands["neighbor"].commands["expected"], ["Ethernet112"]) + result = runner.invoke( + show.cli.commands["interfaces"].commands["neighbor"].commands["expected"], ["Ethernet112"]) print(result.exit_code) print(result.output) # traceback.print_tb(result.exc_info[2]) @@ -258,7 +379,8 @@ def test_show_interfaces_neighbor_expected_etp29(self): def test_show_interfaces_neighbor_expected_t1_Ethernet1_1(self, setup_t1_topo): runner = CliRunner() os.environ['SONIC_CLI_IFACE_MODE'] = "alias" - result = runner.invoke(show.cli.commands["interfaces"].commands["neighbor"].commands["expected"], ["Ethernet1/1"]) + result = runner.invoke( + show.cli.commands["interfaces"].commands["neighbor"].commands["expected"], ["Ethernet1/1"]) os.environ['SONIC_CLI_IFACE_MODE'] = "default" print(result.exit_code) print(result.output) @@ -294,7 +416,7 @@ def test_show_interfaces_portchannel_in_alias_mode(self): traceback.print_tb(result.exc_info[2]) assert result.exit_code == 0 assert result.output == show_interfaces_portchannel_in_alias_mode_output - + @mock.patch('sonic_py_common.multi_asic.get_port_table', mock.MagicMock(return_value={})) def test_supervisor_show_interfaces_alias_etp1_with_waring(self): runner = CliRunner() @@ -337,6 +459,43 @@ 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() + db = Db() + + result = runner.invoke( + config.config.commands["switchport"].commands["mode"], ["routed", "PortChannel0001"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + 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 + + def test_show_interfaces_switchport_config_in_alias_mode(self): + runner = CliRunner() + os.environ['SONIC_CLI_IFACE_MODE'] = "alias" + result = runner.invoke(show.cli.commands["interfaces"].commands["switchport"].commands["config"]) + os.environ['SONIC_CLI_IFACE_MODE'] = "default" + print(result.exit_code) + print(result.output) + + assert result.exit_code == 0 + assert result.output == show_interfaces_switchport_config_in_alias_mode_output + @classmethod def teardown_class(cls): print("TEARDOWN") diff --git a/tests/ip_config_test.py b/tests/ip_config_test.py index 6003e7401a..ffa2931093 100644 --- a/tests/ip_config_test.py +++ b/tests/ip_config_test.py @@ -115,6 +115,14 @@ def test_add_del_interface_valid_ipv4(self): assert ('Ethernet0.10', '10.11.10.1/24') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE') # config int ip remove Eth36.10 32.11.10.1/24 + with mock.patch('utilities_common.cli.run_command') as mock_run_command: + result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["remove"], + ["Eth36.10", "32.11.10.1/24"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert mock_run_command.call_count == 1 + assert ('Eth36.10', '32.11.10.1/24') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + with mock.patch('utilities_common.cli.run_command') as mock_run_command: result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["remove"], ["Eth36.10", "32.11.10.1/24"], obj=obj) print(result.exit_code, result.output) @@ -122,6 +130,7 @@ def test_add_del_interface_valid_ipv4(self): assert mock_run_command.call_count == 1 assert ('Eth36.10', '32.11.10.1/24') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE') + def test_add_interface_invalid_ipv4(self): db = Db() runner = CliRunner() diff --git a/tests/loopback_action_test.py b/tests/loopback_action_test.py index b88d36973d..8c66ee52a0 100644 --- a/tests/loopback_action_test.py +++ b/tests/loopback_action_test.py @@ -116,8 +116,7 @@ def test_config_loopback_action_on_non_ip_interface(self): result = runner.invoke(config.config.commands['interface'].commands["ip"].commands['loopback-action'], [iface, action], obj=obj) print(result.exit_code, result.output) - assert result.exit_code != 0 - assert ERROR_MSG in result.output + assert result.exit_code == 0 def test_config_loopback_action_invalid_action(self): runner = CliRunner() diff --git a/tests/mock_tables/asic0/config_db.json b/tests/mock_tables/asic0/config_db.json index 7b4cac3430..8b867bdc96 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 b2bf54c995..af37538447 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" }, @@ -96,6 +101,7 @@ "lanes": "1,2,3,4", "mtu": "9100", "tpid": "0x8100", + "mode": "trunk", "pfc_asym": "off", "speed": "1000", "role": "Dpc" @@ -108,6 +114,7 @@ "lanes": "5,6,7,8", "mtu": "9100", "tpid": "0x8100", + "mode": "trunk", "pfc_asym": "off", "speed": "1000" }, @@ -119,6 +126,7 @@ "lanes": "13,14,15,16", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -130,6 +138,7 @@ "lanes": "9,10,11,12", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "10" }, @@ -141,6 +150,7 @@ "lanes": "17,18,19,20", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -152,6 +162,7 @@ "lanes": "21,22,23,24", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -163,6 +174,7 @@ "lanes": "53,54,55,56", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -174,6 +186,7 @@ "lanes": "49,50,51,52", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -185,6 +198,7 @@ "lanes": "57,58,59,60", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -196,6 +210,7 @@ "lanes": "61,62,63,64", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -207,6 +222,7 @@ "lanes": "69,70,71,72", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -218,6 +234,7 @@ "lanes": "65,66,67,68", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -229,6 +246,7 @@ "lanes": "73,74,75,76", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -240,6 +258,7 @@ "lanes": "77,78,79,80", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -251,6 +270,7 @@ "lanes": "109,110,111,112", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -262,6 +282,7 @@ "lanes": "105,106,107,108", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -273,6 +294,7 @@ "lanes": "113,114,115,116", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -284,6 +306,7 @@ "lanes": "117,118,119,120", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -295,6 +318,7 @@ "lanes": "125,126,127,128", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -305,6 +329,7 @@ "lanes": "121,122,123,124", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -315,6 +340,7 @@ "lanes": "81,82,83,84", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -325,6 +351,7 @@ "lanes": "85,86,87,88", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -336,6 +363,7 @@ "lanes": "93,94,95,96", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -347,6 +375,7 @@ "lanes": "89,90,91,92", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -358,6 +387,7 @@ "lanes": "101,102,103,104", "mtu": "9100", "tpid": "0x8100", + "mode": "routed", "pfc_asym": "off", "speed": "40000" }, @@ -369,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/mpls_test.py b/tests/mpls_test.py index 57336cd63f..df50c3ded1 100644 --- a/tests/mpls_test.py +++ b/tests/mpls_test.py @@ -122,8 +122,7 @@ def test_config_mpls_invalid_interface_add(self): ) print(result.exit_code) print(result.output) - assert result.exit_code == 2 - assert result.output == invalid_interface_add_output + assert result.exit_code == 0 def test_show_interfaces_mpls_frontend(self): @@ -203,8 +202,7 @@ def test_config_mpls_invalid_interface_remove(self): ) print(result.exit_code) print(result.output) - assert result.exit_code == 2 - assert result.output == invalid_interface_remove_output + assert result.exit_code == 0 @classmethod @@ -253,8 +251,7 @@ def test_config_mpls_masic_invalid_interface_add(self): ) print(result.exit_code) print(result.output) - assert result.exit_code == 2 - assert result.output == invalid_interface_add_output + assert result.exit_code == 0 def test_show_interfaces_mpls_masic_frontend(self): @@ -344,9 +341,7 @@ def test_config_mpls_masic_invalid_interface_remove(self): ) print(result.exit_code) print(result.output) - assert result.exit_code == 2 - assert result.output == invalid_interface_remove_output - + assert result.exit_code == 0 @classmethod def teardown_class(cls): diff --git a/tests/vlan_test.py b/tests/vlan_test.py index 5a84737b2a..2d3c1dcf1b 100644 --- a/tests/vlan_test.py +++ b/tests/vlan_test.py @@ -2,25 +2,28 @@ import traceback import pytest from unittest import mock - from click.testing import CliRunner import config.main as config + import show.main as show from utilities_common.db import Db + + from importlib import reload import utilities_common.bgp_util as bgp_util IP_VERSION_PARAMS_MAP = { "ipv4": { "table": "VLAN" + }, "ipv6": { "table": "DHCP_RELAY" } } -show_vlan_brief_output="""\ +show_vlan_brief_output = """\ +-----------+-----------------+-----------------+----------------+-------------+ | VLAN ID | IP Address | Ports | Port Tagging | Proxy ARP | +===========+=================+=================+================+=============+ @@ -38,7 +41,7 @@ +-----------+-----------------+-----------------+----------------+-------------+ """ -show_vlan_brief_in_alias_mode_output="""\ +show_vlan_brief_in_alias_mode_output = """\ +-----------+-----------------+-----------------+----------------+-------------+ | VLAN ID | IP Address | Ports | Port Tagging | Proxy ARP | +===========+=================+=================+================+=============+ @@ -56,7 +59,7 @@ +-----------+-----------------+-----------------+----------------+-------------+ """ -show_vlan_brief_empty_output="""\ +show_vlan_brief_empty_output = """\ +-----------+-----------------+-----------------+----------------+-------------+ | VLAN ID | IP Address | Ports | Port Tagging | Proxy ARP | +===========+=================+=================+================+=============+ @@ -69,7 +72,7 @@ +-----------+-----------------+-----------------+----------------+-------------+ """ -show_vlan_brief_with_portchannel_output="""\ +show_vlan_brief_with_portchannel_output = """\ +-----------+-----------------+-----------------+----------------+-------------+ | VLAN ID | IP Address | Ports | Port Tagging | Proxy ARP | +===========+=================+=================+================+=============+ @@ -88,7 +91,7 @@ +-----------+-----------------+-----------------+----------------+-------------+ """ -show_vlan_config_output="""\ +show_vlan_config_output = """\ Name VID Member Mode -------- ----- --------------- -------- Vlan1000 1000 Ethernet4 untagged @@ -101,7 +104,7 @@ Vlan4000 4000 PortChannel1001 tagged """ -show_vlan_config_in_alias_mode_output="""\ +show_vlan_config_in_alias_mode_output = """\ Name VID Member Mode -------- ----- --------------- -------- Vlan1000 1000 etp2 untagged @@ -114,7 +117,7 @@ Vlan4000 4000 PortChannel1001 tagged """ -config_add_del_vlan_and_vlan_member_output="""\ +config_add_del_vlan_and_vlan_member_output = """\ +-----------+-----------------+-----------------+----------------+-------------+ | VLAN ID | IP Address | Ports | Port Tagging | Proxy ARP | +===========+=================+=================+================+=============+ @@ -134,7 +137,7 @@ +-----------+-----------------+-----------------+----------------+-------------+ """ -config_add_del_vlan_and_vlan_member_in_alias_mode_output="""\ +config_add_del_vlan_and_vlan_member_in_alias_mode_output = """\ +-----------+-----------------+-----------------+----------------+-------------+ | VLAN ID | IP Address | Ports | Port Tagging | Proxy ARP | +===========+=================+=================+================+=============+ @@ -154,9 +157,148 @@ +-----------+-----------------+-----------------+----------------+-------------+ """ +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 | | | | 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 | ++-----------+-----------------+-----------------+----------------+-------------+ +""" + + +test_config_add_del_with_switchport_modes_changes_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 def setup_class(cls): os.environ['UTILITIES_UNIT_TESTING'] = "1" @@ -207,7 +349,6 @@ def test_show_vlan_brief_in_alias_mode(self): assert result.output == show_vlan_brief_in_alias_mode_output def test_show_vlan_brief_explicit_proxy_arp_disable(self): - runner = CliRunner() db = Db() db.cfgdb.set_entry("VLAN_INTERFACE", "Vlan1000", {"proxy_arp": "disabled"}) @@ -230,85 +371,725 @@ def test_show_vlan_config_in_alias_mode(self): assert result.exit_code == 0 assert result.output == show_vlan_config_in_alias_mode_output + def test_switchport_status(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["interfaces"].commands["switchport"], ["etp33"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: No such command \"etp33\"" in result.output + + def test_show_switchport_status_in_alias_mode(self): + runner = CliRunner() + os.environ['SONIC_CLI_IFACE_MODE'] = "alias" + result = runner.invoke(show.cli.commands["interfaces"].commands["switchport"], ["etp33"]) + os.environ['SONIC_CLI_IFACE_MODE'] = "default" + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: No such command \"etp33\"" in result.output + def test_config_vlan_add_vlan_with_invalid_vlanid(self): runner = CliRunner() result = runner.invoke(config.config.commands["vlan"].commands["add"], ["4096"]) 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() result = runner.invoke(config.config.commands["vlan"].commands["add"], ["1000"]) print(result.exit_code) print(result.output) - assert result.exit_code != 0 - assert "Error: Vlan1000 already exists" in result.output + assert result.exit_code != 0 + assert "Error: Vlan1000 already exists" in result.output + + def test_config_vlan_del_vlan_with_invalid_vlanid(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["4096"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Invalid VLAN ID 4096 (2-4094)" in result.output + + def test_config_vlan_del_vlan_with_nonexist_vlanid(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Vlan1001 does not exist" in result.output + + def test_config_vlan_add_exist_port_member(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], ["1000", "Ethernet4"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Ethernet4 is already a member of Vlan1000" in result.output + + def test_config_vlan_add_rif_portchannel_member(self): + runner = CliRunner() + db = Db() + + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["1000", "PortChannel0001", "--untagged"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: PortChannel0001 is a router interface!" 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_range_with_invalid_vlanid(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["4093-4095", "--multiple"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Invalid VLAN ID must be in (2-4094)" in result.output + + def test_config_vlan_add_vlan_with_multiple_vlanids_is_digit_fail(self): + runner = CliRunner() + vid = "test_fail_case" + result = runner.invoke(config.config.commands["vlan"].commands["add"], + ["{},1001,1002".format(vid), "--multiple"]) + 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_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_del_vlan_is_digit_fail(self): + runner = CliRunner() + vid = "test_fail_case" + result = runner.invoke(config.config.commands["vlan"].commands["del"], [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_default_vlan(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], ["1", "Ethernet4"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Vlan1 is default 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 (2-4094)" in result.output + + def test_config_vlan_del_member_with_invalid_vlanid(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], ["4096", "Ethernet4"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Invalid VLAN ID 4096 (2-4094)" in result.output + + def test_config_vlan_add_member_with_invalid_port(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], ["4097", "Ethernet4"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Invalid VLAN ID 4097 (2-4094)" in result.output + + def test_config_vlan_del_member_with_invalid_port(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], ["4097", "Ethernet4"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Invalid VLAN ID 4097 (2-4094)" in result.output + + def test_config_vlan_add_member_with_nonexist_vlanid(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], ["1001", "Ethernet4"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Vlan1001 does not exist" in result.output + + def test_config_vlan_del_member_with_nonexist_vlanid(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], ["1001", "Ethernet4"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Vlan1001 does not exist" in result.output + + def test_config_vlan_add_member_is_digit_fail(self): + runner = CliRunner() + vid = "test_fail_case" + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], [vid, "Ethernet4"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Vlan is not integer" in result.output + + def test_config_vlan_add_member_with_except_flag_is_digit_fail(self): + runner = CliRunner() + vid = "test_fail_case" + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + [vid, "Ethernet4", "--except_flag"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Vlan is not integer" in result.output + + def test_config_vlan_add_member_multiple_untagged(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["1000,2000", "Ethernet4", "--multiple", "--untagged"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Ethernet4 cannot have more than one untagged Vlan" in result.output + + def test_config_vlan_add_nonexist_port_member(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], ["1000", "Ethernet3"]) + print(result.exit_code) + print(result.output) + 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) + print(result.output) + assert result.exit_code != 0 + assert "Error: PortChannel1011 does not exist" in result.output + + def test_config_vlan_add_mirror_destintion_port_member(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["1000", "Ethernet44"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Ethernet44 is configured as mirror destination port" in result.output + + def test_show_port_switchport_etp33_in_alias_mode(self): + runner = CliRunner() + os.environ["SONIC_CLI_IFACE_MODE"] = "alias" + result = runner.invoke(config.config.commands["switchport"].commands["mode"], + ["trunk", "etp33"]) + os.environ["SONIC_CLI_IFACE_MODE"] = "default" + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: etp33 does not exist" in result.output + + def test_show_port_vlan_etp33_in_alias_mode(self): + runner = CliRunner() + os.environ["SONIC_CLI_IFACE_MODE"] = "alias" + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["4000", "etp33"]) + os.environ["SONIC_CLI_IFACE_MODE"] = "default" + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: etp33 does not exist" in result.output + + def test_show_port_vlan_del_etp33_in_alias_mode(self): + runner = CliRunner() + os.environ["SONIC_CLI_IFACE_MODE"] = "alias" + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], + ["4000", "etp33"]) + os.environ["SONIC_CLI_IFACE_MODE"] = "default" + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: etp33 is not a member of Vlan4000" in result.output + + def test_config_switchport_mode_with_mirror_destintion_port(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["switchport"].commands["mode"], ["trunk", "Ethernet44"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Ethernet44 is configured as mirror destination port" in result.output + + def test_config_vlan_add_portchannel_member_with_switchport_modes(self): + runner = CliRunner() + db = Db() + + # Configure Ethernet112 to trunk mode; should give error as it is part of PortChannel0001 + result = runner.invoke(config.config.commands["switchport"].commands["mode"], ["trunk", "Ethernet112"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Ethernet112 is part of portchannel!" in result.output + + # Configure PortChannel0001 to routed mode + result = runner.invoke(config.config.commands["switchport"].commands["mode"], + ["routed", "PortChannel0001"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # Configure PortChannel0001 to routed mode again; should give error as it is already in routed mode + result = runner.invoke(config.config.commands["switchport"].commands["mode"], + ["routed", "PortChannel0001"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: PortChannel0001 is already in routed mode" in result.output + + # Configure PortChannel0001 to trunk mode; should give error as it is a router interface + result = runner.invoke(config.config.commands["switchport"].commands["mode"], + ["trunk", "PortChannel0001"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Remove IP from PortChannel0001 to change mode!" in result.output + + # Remove PortChannel1001 member from Vlan4000 + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], + ["4000", "PortChannel1001"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # Configure PortChannel1001 to access mode + result = runner.invoke(config.config.commands["switchport"].commands["mode"], + ["access", "PortChannel1001"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # Configure PortChannel1001 back to routed mode + result = runner.invoke(config.config.commands["switchport"].commands["mode"], + ["routed", "PortChannel1001"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # Configure PortChannel1001 to trunk mode + result = runner.invoke(config.config.commands["switchport"].commands["mode"], + ["trunk", "PortChannel1001"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # Add back PortChannel1001 tagged member to Vlan4000 + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["4000", "PortChannel1001"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # Add PortChannel1001 to Vlan1000 + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["1000", "PortChannel1001", "--untagged"], 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_with_portchannel_output + + def test_config_vlan_with_vxlanmap_del_vlan(self, mock_restart_dhcp_relay_service): + runner = CliRunner() + db = Db() + + # create vlan + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["1027"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # create vxlan map + result = runner.invoke(config.config.commands["vxlan"].commands["map"].commands["add"], + ["vtep1", "1027", "11027"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # attempt to del vlan with vxlan map, should fail + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1027"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: vlan: 1027 can not be removed. First remove vxlan mapping" in result.output + + def test_config_vlan_del_vlan(self, mock_restart_dhcp_relay_service): + runner = CliRunner() + db = Db() + obj = {'config_db': db.cfgdb} + + # del vlan with IP + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1000"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Vlan1000 can not be removed. First remove IP addresses assigned to this VLAN\n" in result.output + + # remove vlan IP`s + with mock.patch('utilities_common.cli.run_command') as mock_run_command: + result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["remove"], + ["Vlan1000", "192.168.0.1/21"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert mock_run_command.call_count == 1 + + with mock.patch('utilities_common.cli.run_command') as mock_run_command: + result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["remove"], + ["Vlan1000", "fc02:1000::1/64"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code == 0 + assert mock_run_command.call_count == 1 + + # del vlan with IP + with mock.patch('utilities_common.cli.run_command') as mock_run_command: + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1000"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert ("Error: VLAN ID 1000 can not be removed." + " First remove all members assigned to this VLAN") in result.output + + with mock.patch("config.vlan.delete_db_entry") as delete_db_entry: + vlan_member = db.cfgdb.get_table('VLAN_MEMBER') + keys = [(k, v) for k, v in vlan_member if k == 'Vlan{}'.format(1000)] + for k, v in keys: + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], + ["1000", v], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1000"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + delete_db_entry.assert_has_calls([ + mock.call("DHCPv6_COUNTER_TABLE|Vlan1000", mock.ANY, db.db.STATE_DB), + mock.call("DHCPv6_COUNTER_TABLE|Ethernet4", mock.ANY, db.db.STATE_DB), + mock.call("DHCPv6_COUNTER_TABLE|Ethernet8", mock.ANY, db.db.STATE_DB), + mock.call("DHCPv6_COUNTER_TABLE|Ethernet12", mock.ANY, db.db.STATE_DB), + mock.call("DHCPv6_COUNTER_TABLE|Ethernet16", mock.ANY, db.db.STATE_DB), + mock.call("DHCP_COUNTER_TABLE|Vlan1000", mock.ANY, db.db.STATE_DB), + mock.call("DHCP_COUNTER_TABLE|Ethernet4", mock.ANY, db.db.STATE_DB), + mock.call("DHCP_COUNTER_TABLE|Ethernet8", mock.ANY, db.db.STATE_DB), + mock.call("DHCP_COUNTER_TABLE|Ethernet12", mock.ANY, db.db.STATE_DB), + mock.call("DHCP_COUNTER_TABLE|Ethernet16", mock.ANY, db.db.STATE_DB) + ], any_order=True) + + # 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_empty_output + + def test_config_vlan_del_last_vlan(self): + runner = CliRunner() + db = Db() + db.cfgdb.delete_table("VLAN_MEMBER") + db.cfgdb.delete_table("VLAN_INTERFACE") + db.cfgdb.set_entry("VLAN", "Vlan2000", None) + db.cfgdb.set_entry("VLAN", "Vlan3000", None) + db.cfgdb.set_entry("VLAN", "Vlan4000", None) + + with mock.patch("utilities_common.cli.run_command", mock.Mock(return_value=("", 0))) as mock_run_command: + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1000"], obj=db) + print(result.exit_code) + print(result.output) + mock_run_command.assert_has_calls([ + mock.call(['docker', 'exec', '-i', 'swss', 'supervisorctl', 'status', 'ndppd'], + ignore_error=True, return_cmd=True), + mock.call(['docker', 'exec', '-i', 'swss', 'supervisorctl', 'stop', 'ndppd'], + ignore_error=True, return_cmd=True), + mock.call(['docker', 'exec', '-i', 'swss', 'rm', '-f', '/etc/supervisor/conf.d/ndppd.conf'], + ignore_error=True, return_cmd=True), + mock.call(['docker', 'exec', '-i', 'swss', 'supervisorctl', 'update'], return_cmd=True) + ]) + assert result.exit_code == 0 + + def test_config_vlan_del_nonexist_vlan_member(self): + runner = CliRunner() + + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], + ["1000", "Ethernet0"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Ethernet0 is not a member of Vlan1000" in result.output + + def test_config_add_del_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"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # 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 + + # show output + result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) + print(result.output) + assert result.output == config_add_del_vlan_and_vlan_member_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 + + # 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_in_alias_mode(self, mock_restart_dhcp_relay_service): + runner = CliRunner() + db = Db() + + os.environ['SONIC_CLI_IFACE_MODE'] = "alias" + + # 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 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) + print(result.exit_code) + print(result.output) + assert result.output == config_add_del_vlan_and_vlan_member_in_alias_mode_output + + # remove vlan member + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], + ["1001", "etp6"], 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"], 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_in_alias_mode_output + + 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,1002,1003 + 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 + 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 - def test_config_vlan_del_vlan_with_invalid_vlanid(self): - runner = CliRunner() - result = runner.invoke(config.config.commands["vlan"].commands["del"], ["4096"]) + # 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 - assert "Error: Invalid VLAN ID 4096 (1-4094)" in result.output + assert result.exit_code == 0 - def test_config_vlan_del_vlan_with_nonexist_vlanid(self): - runner = CliRunner() - result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001"]) + # 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 "Error: Vlan1001 does not exist" in result.output + assert result.exit_code == 0 + assert result.output == show_vlan_brief_output - def test_config_vlan_add_member_with_invalid_vlanid(self): + def test_config_add_del_add_vlans_and_add_vlans_member_except_vlan(self, mock_restart_dhcp_relay_service): runner = CliRunner() - result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], ["4096", "Ethernet4"]) + db = Db() + + # 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) - assert result.exit_code != 0 - assert "Error: Invalid VLAN ID 4096 (1-4094)" in result.output + assert result.exit_code == 0 - def test_config_vlan_add_member_with_nonexist_vlanid(self): - runner = CliRunner() - result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], ["1001", "Ethernet4"]) + # add Ethernet20 to all vlans except vlan1000, vlan4000 with 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) print(result.output) - assert result.exit_code != 0 - assert "Error: Vlan1001 does not exist" in result.output + traceback.print_tb(result.exc_info[2]) + assert result.exit_code == 0 - def test_config_vlan_add_exist_port_member(self): - runner = CliRunner() - result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], ["1000", "Ethernet4"]) + # 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,3000", "Ethernet20", "--multiple", "--except_flag"], obj=db) print(result.exit_code) print(result.output) - assert result.exit_code != 0 - assert "Error: Ethernet4 is already a member of Vlan1000" in result.output + assert result.exit_code == 0 - def test_config_vlan_add_nonexist_port_member(self): - runner = CliRunner() - result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], ["1000", "Ethernet3"]) + # remove vlan member except 1001 + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], + ["1001", "Ethernet20", "--except_flag"], obj=db) print(result.exit_code) print(result.output) - assert result.exit_code != 0 - assert "Error: Ethernet3 does not exist" in result.output + assert result.exit_code == 0 - def test_config_vlan_add_nonexist_portchannel_member(self): - runner = CliRunner() - result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], \ - ["1000", "PortChannel1011"]) + # 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 "Error: PortChannel1011 does not exist" in 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 - def test_config_vlan_add_portchannel_member(self): - runner = CliRunner() - db = Db() + # 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 - result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], \ - ["1000", "PortChannel1001", "--untagged"], obj=db) + # del 1001,1002 + 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 @@ -318,152 +1099,109 @@ def test_config_vlan_add_portchannel_member(self): print(result.exit_code) print(result.output) assert result.exit_code == 0 - assert result.output == show_vlan_brief_with_portchannel_output + assert result.output == show_vlan_brief_output - def test_config_vlan_add_rif_portchannel_member(self): + def test_config_add_del_add_vlans_and_add_all_vlan_member(self, mock_restart_dhcp_relay_service): runner = CliRunner() db = Db() - result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], \ - ["1000", "PortChannel0001", "--untagged"], obj=db) + # 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) - assert result.exit_code != 0 - assert "Error: PortChannel0001 is a router interface!" in result.output + assert result.exit_code == 0 - def test_config_vlan_with_vxlanmap_del_vlan(self, mock_restart_dhcp_relay_service): - runner = CliRunner() - db = Db() - obj = {'config_db': db.cfgdb} + # add Ethernet20 to all vlans + 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 - # create vlan - result = runner.invoke(config.config.commands["vlan"].commands["add"], ["1027"], obj=db) + # 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 - # create vxlan map - result = runner.invoke(config.config.commands["vxlan"].commands["map"].commands["add"], ["vtep1", "1027", "11027"], obj=db) + # del 1001, 1002, 1003 + 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 - # attempt to del vlan with vxlan map, should fail - result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1027"], obj=db) + # 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 "Error: vlan: 1027 can not be removed. First remove vxlan mapping" in result.output + assert result.exit_code == 0 + assert result.output == show_vlan_brief_output - def test_config_vlan_del_vlan(self, mock_restart_dhcp_relay_service): + def test_config_add_del_vlan_and_vlan_member_with_switchport_modes(self, mock_restart_dhcp_relay_service): runner = CliRunner() db = Db() - obj = {'config_db':db.cfgdb} - # del vlan with IP - result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1000"], obj=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 - assert "Error: Vlan1000 can not be removed. First remove IP addresses assigned to this VLAN" in result.output + assert result.exit_code == 0 - # remove vlan IP`s - with mock.patch('utilities_common.cli.run_command') as mock_run_command: - result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["remove"], ["Vlan1000", "192.168.0.1/21"], obj=obj) - print(result.exit_code, result.output) - assert result.exit_code == 0 - assert mock_run_command.call_count == 1 + # configure Ethernet20 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 to routed mode" in result.output - with mock.patch('utilities_common.cli.run_command') as mock_run_command: - result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["remove"], ["Vlan1000", "fc02:1000::1/64"], obj=obj) - print(result.exit_code, result.output) - assert result.exit_code == 0 - assert mock_run_command.call_count == 1 + # configure Ethernet20 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 to access mode" in result.output - # del vlan with IP - result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1000"], obj=db) + # configure Ethernet20 to access mode again; should give error as it is already in 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 "Error: VLAN ID 1000 can not be removed. First remove all members assigned to this VLAN." in result.output - - with mock.patch("config.vlan.delete_db_entry") as delete_db_entry: - vlan_member = db.cfgdb.get_table('VLAN_MEMBER') - keys = [ (k, v) for k, v in vlan_member if k == 'Vlan{}'.format(1000) ] - for k,v in keys: - result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], ["1000", v], obj=db) - print(result.exit_code) - print(result.output) - assert result.exit_code == 0 - - result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1000"], obj=db) - print(result.exit_code) - print(result.output) - assert result.exit_code == 0 - delete_db_entry.assert_has_calls([ - mock.call("DHCPv6_COUNTER_TABLE|Vlan1000", mock.ANY, db.db.STATE_DB), - mock.call("DHCPv6_COUNTER_TABLE|Ethernet4", mock.ANY, db.db.STATE_DB), - mock.call("DHCPv6_COUNTER_TABLE|Ethernet8", mock.ANY, db.db.STATE_DB), - mock.call("DHCPv6_COUNTER_TABLE|Ethernet12", mock.ANY, db.db.STATE_DB), - mock.call("DHCPv6_COUNTER_TABLE|Ethernet16", mock.ANY, db.db.STATE_DB), - mock.call("DHCP_COUNTER_TABLE|Vlan1000", mock.ANY, db.db.STATE_DB), - mock.call("DHCP_COUNTER_TABLE|Ethernet4", mock.ANY, db.db.STATE_DB), - mock.call("DHCP_COUNTER_TABLE|Ethernet8", mock.ANY, db.db.STATE_DB), - mock.call("DHCP_COUNTER_TABLE|Ethernet12", mock.ANY, db.db.STATE_DB), - mock.call("DHCP_COUNTER_TABLE|Ethernet16", mock.ANY, db.db.STATE_DB) - ], any_order=True) + assert "Ethernet20 is already in access mode" in result.output - # show output - result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) + # 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 result.output == show_vlan_brief_empty_output - - def test_config_vlan_del_last_vlan(self): - runner = CliRunner() - db = Db() - db.cfgdb.delete_table("VLAN_MEMBER") - db.cfgdb.delete_table("VLAN_INTERFACE") - db.cfgdb.set_entry("VLAN", "Vlan2000", None) - db.cfgdb.set_entry("VLAN", "Vlan3000", None) - db.cfgdb.set_entry("VLAN", "Vlan4000", None) - - with mock.patch("utilities_common.cli.run_command", mock.Mock(return_value=("", 0))) as mock_run_command: - result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1000"], obj=db) - print(result.exit_code) - print(result.output) - mock_run_command.assert_has_calls([ - mock.call(['docker', 'exec', '-i', 'swss', 'supervisorctl', 'status', 'ndppd'], ignore_error=True, return_cmd=True), - mock.call(['docker', 'exec', '-i', 'swss', 'supervisorctl', 'stop', 'ndppd'], ignore_error=True, return_cmd=True), - mock.call(['docker', 'exec', '-i', 'swss', 'rm', '-f', '/etc/supervisor/conf.d/ndppd.conf'], ignore_error=True, return_cmd=True), - mock.call(['docker', 'exec', '-i', 'swss', 'supervisorctl', 'update'], return_cmd=True) - ]) - assert result.exit_code == 0 - - def test_config_vlan_del_nonexist_vlan_member(self): - runner = CliRunner() - result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], \ - ["1000", "Ethernet0"]) + # 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 "Error: Ethernet0 is not a member of Vlan1000" in result.output - - def test_config_add_del_vlan_and_vlan_member(self, mock_restart_dhcp_relay_service): - runner = CliRunner() - db = Db() + assert "Ethernet20 is in access mode! Tagged Members cannot be added" in result.output - # add vlan 1001 - result = runner.invoke(config.config.commands["vlan"].commands["add"], ["1001"], obj=db) + # 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 to trunk mode" in result.output - # add Ethernet20 to vlan 1001 + # add Ethernet20 to vlan 1001 as tagged member result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], - ["1001", "Ethernet20", "--untagged"], obj=db) + ["1000", "Ethernet20"], obj=db) print(result.exit_code) print(result.output) traceback.print_tb(result.exc_info[2]) @@ -472,16 +1210,44 @@ def test_config_add_del_vlan_and_vlan_member(self, mock_restart_dhcp_relay_servi # show output result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) print(result.output) - assert result.output == config_add_del_vlan_and_vlan_member_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"], - ["1001", "Ethernet20"], obj=db) + ["1000", "Ethernet20"], obj=db) print(result.exit_code) print(result.output) assert result.exit_code == 0 - # add del 1001 + # 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 to routed mode" in result.output + + # del 1001 result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001"], obj=db) print(result.exit_code) print(result.output) @@ -494,53 +1260,76 @@ def test_config_add_del_vlan_and_vlan_member(self, mock_restart_dhcp_relay_servi assert result.exit_code == 0 assert result.output == show_vlan_brief_output - def test_config_add_del_vlan_and_vlan_member_in_alias_mode(self, mock_restart_dhcp_relay_service): + def test_config_add_del_with_switchport_modes_changes_output( + self, mock_restart_dhcp_relay_service): runner = CliRunner() db = Db() - os.environ['SONIC_CLI_IFACE_MODE'] = "alias" - # 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 etp6 to vlan 1001 + # configure Ethernet20 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 to trunk mode" in result.output + + # 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 to trunk mode" in result.output + + # add Ethernet64 to vlan 1001 result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], - ["1001", "etp6", "--untagged"], obj=db) + ["1001", "Ethernet64"], 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) + # configure Ethernet64 from trunk 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.output == config_add_del_vlan_and_vlan_member_in_alias_mode_output + assert result.exit_code != 0 + assert "Ethernet64 is in trunk mode and have tagged member(s)." in result.output # remove vlan member result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], - ["1001", "etp6"], obj=db) + ["1001", "Ethernet64"], 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"], obj=db) + # 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 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 == show_vlan_brief_in_alias_mode_output - - os.environ['SONIC_CLI_IFACE_MODE'] = "default" + assert result.output == ( + test_config_add_del_with_switchport_modes_changes_output) def test_config_vlan_proxy_arp_with_nonexist_vlan_intf_table(self): modes = ["enabled", "disabled"] @@ -572,8 +1361,9 @@ def test_config_vlan_proxy_arp_with_nonexist_vlan_intf(self): assert "Interface Vlan1001 does not exist" in result.output def test_config_vlan_proxy_arp_enable(self): - mock_cli_returns = [("running", 0),("", 1)] + [("", 0)] * 4 - with mock.patch("utilities_common.cli.run_command", mock.Mock(side_effect=mock_cli_returns)) as mock_run_command: + mock_cli_returns = [("running", 0), ("", 1)] + [("", 0)] * 4 + with mock.patch( + "utilities_common.cli.run_command", mock.Mock(side_effect=mock_cli_returns)) as mock_run_command: runner = CliRunner() db = Db() @@ -582,15 +1372,20 @@ def test_config_vlan_proxy_arp_enable(self): print(result.exit_code) print(result.output) - expected_calls = [mock.call(['docker', 'container', 'inspect', '-f', '{{.State.Status}}', 'swss'], return_cmd=True), - mock.call(['docker', 'exec', '-i', 'swss', 'supervisorctl', 'status', 'ndppd'], ignore_error=True, return_cmd=True), - mock.call(['docker', 'exec', '-i', 'swss', 'cp', '/usr/share/sonic/templates/ndppd.conf', '/etc/supervisor/conf.d/']), + expected_calls = [mock.call(['docker', 'container', 'inspect', '-f', '{{.State.Status}}', 'swss'], + return_cmd=True), + mock.call(['docker', 'exec', '-i', 'swss', 'supervisorctl', 'status', 'ndppd'], + ignore_error=True, return_cmd=True), + mock.call(['docker', 'exec', '-i', 'swss', 'cp', '/usr/share/sonic/templates/ndppd.conf', + '/etc/supervisor/conf.d/']), mock.call(['docker', 'exec', '-i', 'swss', 'supervisorctl', 'update'], return_cmd=True), - mock.call(['docker', 'exec', '-i', 'swss', 'sonic-cfggen', '-d', '-t', '/usr/share/sonic/templates/ndppd.conf.j2,/etc/ndppd.conf']), - mock.call(['docker', 'exec', '-i', 'swss', 'supervisorctl', 'restart', 'ndppd'], return_cmd=True)] + mock.call(['docker', 'exec', '-i', 'swss', 'sonic-cfggen', '-d', '-t', + '/usr/share/sonic/templates/ndppd.conf.j2,/etc/ndppd.conf']), + mock.call(['docker', 'exec', '-i', 'swss', 'supervisorctl', 'restart', 'ndppd'], + return_cmd=True)] mock_run_command.assert_has_calls(expected_calls) - assert result.exit_code == 0 + assert result.exit_code == 0 assert db.cfgdb.get_entry("VLAN_INTERFACE", "Vlan1000") == {"proxy_arp": "enabled"} def test_config_vlan_proxy_arp_disable(self): @@ -604,41 +1399,41 @@ def test_config_vlan_proxy_arp_disable(self): assert result.exit_code == 0 assert db.cfgdb.get_entry("VLAN_INTERFACE", "Vlan2000") == {"proxy_arp": "disabled"} - + def test_config_2_untagged_vlan_on_same_interface(self): runner = CliRunner() db = Db() - + # add Ethernet4 to vlan 2000 as untagged - should fail as ethrnet4 is already untagged member in 1000 result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], - ["2000", "Ethernet4", "--untagged"], obj=db) + ["2000", "Ethernet4", "--untagged"], obj=db) print(result.exit_code) assert result.exit_code != 0 - + # add Ethernet4 to vlan 2000 as tagged - should succeed result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], - ["2000", "Ethernet4" ], obj=db) + ["2000", "Ethernet4"], obj=db) print(result.exit_code) assert result.exit_code == 0 - - def test_config_set_router_port_on_member_interface(self): + + def test_config_set_router_port_on_member_interface(self): db = Db() runner = CliRunner() - obj = {'config_db':db.cfgdb} - + obj = {'config_db': db.cfgdb} + # intf enable result = runner.invoke(config.config.commands["interface"].commands["ip"].commands["add"], - ["Ethernet4", "10.10.10.1/24"], obj=obj) + ["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 in trunk mode and needs to be in routed mode!' in result.output + def test_config_vlan_add_member_of_portchannel(self): runner = CliRunner() db = Db() - result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], \ - ["1000", "Ethernet32", "--untagged"], obj=db) + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["1000", "Ethernet32", "--untagged"], obj=db) print(result.exit_code) print(result.output) assert result.exit_code != 0 @@ -659,14 +1454,12 @@ def test_config_add_del_vlan_dhcp_relay_with_empty_entry(self, ip_version, mock_ assert db.cfgdb.get_entry(IP_VERSION_PARAMS_MAP[ip_version]["table"], "Vlan1001") == exp_output # del vlan 1001 - with mock.patch("utilities_common.dhcp_relay_util.handle_restart_dhcp_relay_service") as mock_handle_restart: - result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001"], obj=db) - print(result.exit_code) - print(result.output) - - assert result.exit_code == 0 - assert "Vlan1001" not in db.cfgdb.get_keys(IP_VERSION_PARAMS_MAP[ip_version]["table"]) - assert "Restart service dhcp_relay failed with error" not in result.output + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert "Vlan1001" not in db.cfgdb.get_keys(IP_VERSION_PARAMS_MAP[ip_version]["table"]) + assert "Restart service dhcp_relay failed with error" not in result.output @pytest.mark.parametrize("ip_version", ["ipv4", "ipv6"]) def test_config_add_del_vlan_dhcp_relay_with_non_empty_entry(self, ip_version, mock_restart_dhcp_relay_service): @@ -796,20 +1589,21 @@ def test_config_vlan_del_dhcp_relay_restart(self): assert result.exit_code == 0 assert mock_run_command.call_count == 1 - # remove vlan members - vlan_member = db.cfgdb.get_table("VLAN_MEMBER") - keys = [(k, v) for k, v in vlan_member if k == "Vlan{}".format(1000)] + # remove vlan s + vlan_ = db.cfgdb.get_table("VLAN_") + keys = [(k, v) for k, v in vlan_ if k == "Vlan{}".format(1000)] for _, v in keys: - result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], ["1000", v], obj=db) + result = runner.invoke(config.config.commands["vlan"].commands[""].commands["del"], ["1000", v], obj=db) print(result.exit_code) print(result.output) assert result.exit_code == 0 origin_run_command_func = config.vlan.clicommon.run_command config.vlan.clicommon.run_command = mock.MagicMock(return_value=("active", 0)) - result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1000"], obj=db) - print(result.exit_code) - print(result.output) - assert result.exit_code == 0 + with mock.patch('utilities_common.cli.run_command') as mock_run_command: + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1000"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 config.vlan.clicommon.run_command = origin_run_command_func diff --git a/utilities_common/cli.py b/utilities_common/cli.py index 9d3cdae710..63336377a8 100644 --- a/utilities_common/cli.py +++ b/utilities_common/cli.py @@ -15,11 +15,11 @@ from sonic_py_common import multi_asic from utilities_common.db import Db from utilities_common.general import load_db_config -from sonic_py_common.general import getstatusoutput_noshell_pipe VLAN_SUB_INTERFACE_SEPARATOR = '.' pass_db = click.make_pass_decorator(Db, ensure=True) + class AbbreviationGroup(click.Group): """This subclass of click.Group supports abbreviated subgroup/subcommand names """ @@ -76,14 +76,15 @@ def read_config(self, filename): except configparser.NoSectionError: pass + # Global Config object _config = None + class AliasedGroup(click.Group): """This subclass of click.Group supports abbreviations and looking up aliases in a config file with a bit of magic. """ - def get_command(self, ctx, cmd_name): global _config @@ -117,6 +118,7 @@ def get_command(self, ctx, cmd_name): return click.Group.get_command(self, ctx, matches[0]) ctx.fail('Too many matches: %s' % ', '.join(sorted(matches))) + class InterfaceAliasConverter(object): """Class which handles conversion between interface name and alias""" @@ -131,7 +133,6 @@ def __init__(self, db=None): self.port_dict = self.config_db.get_table('PORT') self.alias_max_length = 0 - if not self.port_dict: self.port_dict = {} @@ -139,7 +140,7 @@ def __init__(self, db=None): try: if self.alias_max_length < len( self.port_dict[port_name]['alias']): - self.alias_max_length = len( + self.alias_max_length = len( self.port_dict[port_name]['alias']) except KeyError: break @@ -185,15 +186,18 @@ def alias_to_name(self, interface_alias): # interface_alias not in port_dict. Just return interface_alias return interface_alias if sub_intf_sep_idx == -1 else interface_alias + VLAN_SUB_INTERFACE_SEPARATOR + vlan_id + # 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 @@ -205,6 +209,7 @@ def is_ipaddress(val): return False return True + def ipaddress_type(val): """ Return the IP address type """ if not val: @@ -217,6 +222,7 @@ def ipaddress_type(val): return ip_version.version + def is_ip_prefix_in_key(key): ''' Function to check if IP address is present in the key. If it @@ -225,6 +231,7 @@ def is_ip_prefix_in_key(key): ''' return (isinstance(key, tuple)) + def is_valid_port(config_db, port): """Check if port is in PORT table""" @@ -234,6 +241,7 @@ def is_valid_port(config_db, port): return False + def is_valid_portchannel(config_db, port): """Check if port is in PORT_CHANNEL table""" @@ -243,6 +251,7 @@ def is_valid_portchannel(config_db, port): return False + def is_vlanid_in_range(vid): """Check if vlan id is valid or not""" @@ -251,6 +260,7 @@ def is_vlanid_in_range(vid): return False + def check_if_vlanid_exist(config_db, vlan, table_name='VLAN'): """Check if vlan id exits in the config db or ot""" @@ -259,6 +269,7 @@ def check_if_vlanid_exist(config_db, vlan, table_name='VLAN'): return False + def is_port_vlan_member(config_db, port, vlan): """Check if port is a member of vlan""" @@ -269,26 +280,137 @@ def is_port_vlan_member(config_db, port, vlan): return False + +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 interface_is_in_vlan(vlan_member_table, interface_name): """ Check if an interface is in a vlan """ - for _,intf in vlan_member_table: + 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: + 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""" @@ -299,6 +421,7 @@ def is_port_router_interface(config_db, port): return False + def is_pc_router_interface(config_db, pc): """Check if portchannel is a router interface""" @@ -309,15 +432,79 @@ def is_pc_router_interface(config_db, pc): 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) + vlan_member_table = db.cfgdb.get_table('VLAN_MEMBER') + + vlan_member_keys = [] + for _, key in vlan_member_table: + vlan_member_keys.append(key) + + switchport_mode = 'routed' + if "mode" in port: + switchport_mode = port['mode'] + elif "mode" in portchannel: + switchport_mode = portchannel['mode'] + elif interface in vlan_member_keys: + switchport_mode = 'trunk' + return switchport_mode + + 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') - for _,v in mirror_table.items(): + for _, v in mirror_table.items(): if 'dst_port' in v and v['dst_port'] == port: return True return False + def vni_id_is_valid(vni): """Check if the vni id is in acceptable range (between 1 and 2^24) """ @@ -327,6 +514,7 @@ def vni_id_is_valid(vni): return True + def is_vni_vrf_mapped(db, vni): """Check if the vni is mapped to vrf """ @@ -335,10 +523,10 @@ def is_vni_vrf_mapped(db, vni): vrf_table = db.cfgdb.get_table('VRF') vrf_keys = vrf_table.keys() if vrf_keys is not None: - for vrf_key in vrf_keys: - if ('vni' in vrf_table[vrf_key] and vrf_table[vrf_key]['vni'] == vni): - found = 1 - break + for vrf_key in vrf_keys: + if ('vni' in vrf_table[vrf_key] and vrf_table[vrf_key]['vni'] == vni): + found = 1 + break if (found == 1): print("VNI {} mapped to Vrf {}, Please remove VRF VNI mapping".format(vni, vrf_key)) @@ -346,9 +534,10 @@ def is_vni_vrf_mapped(db, vni): return True + def interface_has_mirror_config(mirror_table, interface_name): """Check if port is already configured with mirror config """ - for _,v in mirror_table.items(): + for _, v in mirror_table.items(): if 'src_port' in v and v['src_port'] == interface_name: return True if 'dst_port' in v and v['dst_port'] == interface_name: @@ -356,6 +545,7 @@ def interface_has_mirror_config(mirror_table, interface_name): return False + def print_output_in_alias_mode(output, index): """Convert and print all instances of SONiC interface name to vendor-sepecific interface aliases. @@ -368,7 +558,7 @@ def print_output_in_alias_mode(output, index): if output.startswith("---"): word = output.split() dword = word[index] - if(len(dword) > iface_alias_converter.alias_max_length): + if (len(dword) > iface_alias_converter.alias_max_length): dword = dword[:len(dword) - iface_alias_converter.alias_max_length] underline = dword.rjust(iface_alias_converter.alias_max_length, '-') @@ -381,8 +571,8 @@ def print_output_in_alias_mode(output, index): interface_name = word[index] interface_name = interface_name.replace(':', '') for port_name in natsorted(list(iface_alias_converter.port_dict.keys())): - if interface_name == port_name: - alias_name = iface_alias_converter.port_dict[port_name]['alias'] + if interface_name == port_name: + alias_name = iface_alias_converter.port_dict[port_name]['alias'] if alias_name: if len(alias_name) < iface_alias_converter.alias_max_length: alias_name = alias_name.rjust( @@ -391,6 +581,7 @@ def print_output_in_alias_mode(output, index): click.echo(output.rstrip('\n')) + def run_command_in_alias_mode(command, shell=False): """Run command and replace all instances of SONiC interface names in output with vendor-sepecific interface aliases. @@ -491,8 +682,8 @@ def run_command_in_alias_mode(command, shell=False): """Show ip(v6) int""" index = 0 if output.startswith("Interface"): - output = output.replace("Interface", "Interface".rjust( - iface_alias_converter.alias_max_length)) + output = output.replace("Interface", "Interface".rjust( + iface_alias_converter.alias_max_length)) print_output_in_alias_mode(output, index) else: @@ -505,8 +696,8 @@ def run_command_in_alias_mode(command, shell=False): converted_output = raw_output for port_name in iface_alias_converter.port_dict: converted_output = re.sub(r"(^|\s){}($|,{{0,1}}\s)".format(port_name), - r"\1{}\2".format(iface_alias_converter.name_to_alias(port_name)), - converted_output) + r"\1{}\2".format(iface_alias_converter.name_to_alias(port_name)), + converted_output) click.echo(converted_output.rstrip('\n')) rc = process.poll() @@ -531,14 +722,15 @@ def run_command(command, display_cmd=False, ignore_error=False, return_cmd=False command_str = ' '.join(command) else: command_str = command - if display_cmd == True: + if display_cmd is True: click.echo(click.style("Running command: ", fg='cyan') + click.style(command_str, fg='green')) # No conversion needed for intfutil commands as it already displays # both SONiC interface name and alias name for all interfaces. - # IP route table cannot be handled in function run_command_in_alias_mode since it is in JSON format - # with a list for next hops - if get_interface_naming_mode() == "alias" and not command_str.startswith("intfutil") and not re.search("show ip|ipv6 route", command_str): + # IP route table cannot be handled in function run_command_in_alias_mode since it is in JSON format + # with a list for next hops + if (get_interface_naming_mode() == "alias" and not command_str.startswith("intfutil") and not re.search( + "show ip|ipv6 route", command_str)): run_command_in_alias_mode(command, shell=shell) sys.exit(0) @@ -593,20 +785,21 @@ def interface_is_untagged_member(db, interface_name): """ Check if interface is already untagged member""" vlan_member_table = db.get_table('VLAN_MEMBER') - for key,val in vlan_member_table.items(): - if(key[1] == interface_name): + for key, val in vlan_member_table.items(): + if (key[1] == interface_name): if (val['tagging_mode'] == 'untagged'): return True return False + def is_interface_in_config_db(config_db, interface_name): """ Check if an interface is in CONFIG DB """ - if (not interface_name in config_db.get_keys('VLAN_INTERFACE') and - not interface_name in config_db.get_keys('INTERFACE') and - not interface_name in config_db.get_keys('PORTCHANNEL_INTERFACE') and - not interface_name in config_db.get_keys('VLAN_SUB_INTERFACE') and - not interface_name == 'null'): - return False + if (interface_name not in config_db.get_keys('VLAN_INTERFACE') and + interface_name not in config_db.get_keys('INTERFACE') and + interface_name not in config_db.get_keys('PORTCHANNEL_INTERFACE') and + interface_name not in config_db.get_keys('VLAN_SUB_INTERFACE') and + interface_name == 'null'): + return False return True @@ -625,7 +818,8 @@ def get_help_record(self, ctx): """Return help string with mutually_exclusive list added.""" help_record = list(super(MutuallyExclusiveOption, self).get_help_record(ctx)) if self.mutually_exclusive: - mutually_exclusive_str = 'NOTE: this argument is mutually exclusive with arguments: %s' % ', '.join(self.mutually_exclusive) + mutually_exclusive_str = ( + 'NOTE: this argument is mutually exclusive with arguments: %s' % ', '.join(self.mutually_exclusive)) if help_record[-1]: help_record[-1] += ' ' + mutually_exclusive_str else: @@ -637,7 +831,8 @@ def handle_parse_result(self, ctx, opts, args): for opt_name in self.mutually_exclusive: if opt_name in opts and opts[opt_name] is not None: raise click.UsageError( - "Illegal usage: %s is mutually exclusive with arguments %s" % (self.name, ', '.join(self.mutually_exclusive)) + "Illegal usage: %s is mutually exclusive with arguments %s" % ( + self.name, ', '.join(self.mutually_exclusive)) ) return super(MutuallyExclusiveOption, self).handle_parse_result(ctx, opts, args) @@ -684,7 +879,8 @@ def __init__(self, app_name=None, tag=None): """ Initialize UserCache and create a cache directory if it does not exist. Args: - tag (str): Tag the user cache. Different tags correspond to different cache directories even for the same user. + tag (str): Tag the user cache. Different tags correspond + to different cache directories even for the same user. """ self.uid = os.getuid() self.app_name = os.path.basename(sys.argv[0]) if app_name is None else app_name