Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for Switch Port Modes and VLAN CLI Enhancement #3108

Merged
merged 19 commits into from
Mar 1, 2024
Merged
40 changes: 33 additions & 7 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
from .config_mgmt import ConfigMgmtDPB, ConfigMgmt
from . import mclag
from . import syslog
from . import switchport
from . import dns

# mock masic APIs for unit test
Expand Down Expand Up @@ -105,6 +106,7 @@
PORT_SPEED = "speed"
PORT_TPID = "tpid"
DEFAULT_TPID = "0x8100"
PORT_MODE= "switchport_mode"

asic_type = None

Expand Down Expand Up @@ -1211,6 +1213,9 @@ def config(ctx):
# DNS module
config.add_command(dns.dns)

# Switchport module
config.add_command(switchport.switchport)

@config.command()
@click.option('-y', '--yes', is_flag=True, callback=_abort_if_false,
expose_value=False, prompt='Existing files will be overwritten, continue?')
Expand Down Expand Up @@ -4532,19 +4537,40 @@ 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this check removed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have added a check for switchport mode routed, a routed port fulfill this behavior that a interface has a vlan membership or not. So this check becomes redundant after addition of new check.

# 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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can display the current mode in the error message.

return


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

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


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


@switchport.command("mode")
@click.argument("type", metavar="<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
Loading