Skip to content

Commit

Permalink
Switch Port Modes and VLAN CLI Enhancement (#2419)
Browse files Browse the repository at this point in the history
#### What I did

- Modified "/config/vlan.py" to add support for multiple vids, range of vids in ```vlan add|del``` and ```vlan member add|del```

- Creation of "/config/switchport.py" which will deal with port modes for interfaces i.e. access, trunk and routed

- Created the ```show interface switchport status``` and ```show interface switchport config``` commands so that it shows the status of port either being access, trunk or routed.

- Modified ```Command-Reference.md``` to add new commands in it with usage and examples

- Added new utility functions in "/utilities_common/cli.py" to use in vlan.py and switchport.py commands


- Modified test cases and added new test cases for the new commands

#### How I did it

- Added new commands and modified the existing commands in "/config/vlan.py" to 
    -  accept multiple vids 
    -  accept range of vids 
    -  ```except_flag``` and ```all option``` in vlan member command

- Creation of "/config/switchport.py" file to add commands which will deal with port modes i.e. access, trunk or routed which we were unable to define or assign in SONiC before

- New utility functions in "/utilities_common/cli.py"

- Using ```show interface switchport status``` and ```show interface switchport config``` commands, it will display if an interface is in access or trunk  or routed mode.

#### How to verify it

New commands have been added in Command-Reference.md All the syntax and examples have been added there and they can be verified by running the specific command
  • Loading branch information
MuhammadUmarAsad authored Aug 24, 2023
1 parent c523ff2 commit a0b1cdc
Show file tree
Hide file tree
Showing 19 changed files with 1,982 additions and 209 deletions.
38 changes: 31 additions & 7 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
from .config_mgmt import ConfigMgmtDPB, ConfigMgmt
from . import mclag
from . import syslog
from . import switchport
from . import dns

# mock masic APIs for unit test
Expand Down Expand Up @@ -101,6 +102,8 @@
CFG_PORTCHANNEL_NO="<0-9999>"

PORT_MTU = "mtu"
PORT_MODE= "switchport_mode"

PORT_SPEED = "speed"
PORT_TPID = "tpid"
DEFAULT_TPID = "0x8100"
Expand Down Expand Up @@ -1190,6 +1193,7 @@ def config(ctx):
config.add_command(nat.nat)
config.add_command(vlan.vlan)
config.add_command(vxlan.vxlan)
config.add_command(switchport.switchport)

#add mclag commands
config.add_command(mclag.mclag)
Expand Down Expand Up @@ -4499,19 +4503,39 @@ 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 = "routed"
if is_port:
interface_data = config_db.get_entry('PORT',interface_name)
elif not is_port:
interface_data = config_db.get_entry('PORTCHANNEL',interface_name)

if "mode" in interface_data:
interface_mode = interface_data["mode"]

if interface_mode != "routed":
ctx.fail("Interface {} is not in routed mode!".format(interface_name))
return

try:
ip_address = ipaddress.ip_interface(ip_addr)
except ValueError as err:
Expand Down
138 changes: 138 additions & 0 deletions config/switchport.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import click
from .utils import log
import utilities_common.cli as clicommon

#
# 'switchport' mode ('config switchport ...')
#


@click.group(cls=clicommon.AbbreviationGroup, name='switchport')
def switchport():
"""Switchport mode configuration tasks"""
pass


@switchport.command("mode")
@click.argument("type", metavar="<mode_type>", required=True, type=click.Choice(["access", "trunk", "routed"]))
@click.argument("port", metavar="port", required=True)
@clicommon.pass_db
def switchport_mode(db, type, port):
"""switchport mode help commands.Mode_type can be access or trunk or routed"""

ctx = click.get_current_context()

log.log_info("'switchport mode {} {}' executing...".format(type, port))
mode_exists_status = True

# checking if port name with alias exists
if clicommon.get_interface_naming_mode() == "alias":
alias = port
iface_alias_converter = clicommon.InterfaceAliasConverter(db)
port = iface_alias_converter.alias_to_name(port)
if port is None:
ctx.fail("cannot find port name for alias {}".format(alias))

if clicommon.is_port_mirror_dst_port(db.cfgdb, port):
ctx.fail("{} is configured as mirror destination port".format(port))


if clicommon.is_valid_port(db.cfgdb, port):
is_port = True
elif clicommon.is_valid_portchannel(db.cfgdb, port):
is_port = False
else:
ctx.fail("{} does not exist".format(port))

portchannel_member_table = db.cfgdb.get_table('PORTCHANNEL_MEMBER')

if (is_port and clicommon.interface_is_in_portchannel(portchannel_member_table, port)):
ctx.fail("{} is part of portchannel!".format(port))

if is_port:
port_data = db.cfgdb.get_entry('PORT',port)
else:
port_data = db.cfgdb.get_entry('PORTCHANNEL',port)

# mode type is either access or trunk
if type != "routed":

if "mode" in port_data:
existing_mode = port_data["mode"]
else:
existing_mode = "routed"
mode_exists_status = False
if (is_port and clicommon.is_port_router_interface(db.cfgdb, port)) or \
(not is_port and clicommon.is_pc_router_interface(db.cfgdb, port)):
ctx.fail("Remove IP from {} to change mode!".format(port))

if existing_mode == "routed":
if mode_exists_status:
# if the port in an interface
if is_port:
db.cfgdb.mod_entry("PORT", port, {"mode": "{}".format(type)})
# if not port then is a port channel
elif not is_port:
db.cfgdb.mod_entry("PORTCHANNEL", port, {"mode": "{}".format(type)})

if not mode_exists_status:
port_data["mode"] = type
if is_port:
db.cfgdb.set_entry("PORT", port, port_data)
# if not port then is a port channel
elif not is_port:
db.cfgdb.set_entry("PORTCHANNEL", port, port_data)

if existing_mode == type:
ctx.fail("{} is already in the {} mode".format(port,type))
else:
if existing_mode == "access" and type == "trunk":
pass
if existing_mode == "trunk" and type == "access":
if clicommon.interface_is_tagged_member(db.cfgdb,port):
ctx.fail("{} is in {} mode and have tagged member(s).\nRemove tagged member(s) from {} to switch to {} mode".format(port,existing_mode,port,type))
if is_port:
db.cfgdb.mod_entry("PORT", port, {"mode": "{}".format(type)})
# if not port then is a port channel
elif not is_port:
db.cfgdb.mod_entry("PORTCHANNEL", port, {"mode": "{}".format(type)})

click.echo("{} switched from {} to {} mode".format(port, existing_mode, type))

# if mode type is routed
else:

if clicommon.interface_is_tagged_member(db.cfgdb,port):
ctx.fail("{} has tagged member(s). \nRemove them to change mode to {}".format(port,type))

if clicommon.interface_is_untagged_member(db.cfgdb,port):
ctx.fail("{} has untagged member. \nRemove it to change mode to {}".format(port,type))

if "mode" in port_data:
existing_mode = port_data["mode"]
else:
existing_mode = "routed"
mode_exists_status = False

if not mode_exists_status:
port_data["mode"] = type
if is_port:
db.cfgdb.set_entry("PORT", port, port_data)

# if not port then is a port channel
elif not is_port:
db.cfgdb.set_entry("PORTCHANNEL", port, port_data)
pass

elif mode_exists_status and existing_mode == type:
ctx.fail("{} is already in {} mode".format(port,type))

else:
if is_port:
db.cfgdb.mod_entry("PORT", port, {"mode": "{}".format(type)})
# if not port then is a port channel
elif not is_port:
db.cfgdb.mod_entry("PORTCHANNEL", port, {"mode": "{}".format(type)})

click.echo("{} switched from {} to {} mode".format(port,existing_mode,type))

Loading

0 comments on commit a0b1cdc

Please sign in to comment.