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

mclag enhancements as per HLD at Azure/SONIC#596 #1138

Merged
merged 74 commits into from
Jul 16, 2021
Merged
Show file tree
Hide file tree
Changes from 73 commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
73f90d2
mclagsyncd enhancements as per HLD at Azure/SONIC#596
gitsabari Sep 28, 2020
e31a0e4
Updated code.
tapashdas Nov 11, 2020
82dedb0
addressed LGTM alert
gitsabari Nov 18, 2020
4f6aaee
Updated code.
tapashdas Nov 24, 2020
303d277
Merge branch 'master' into mclag_enhacements
gitsabari Dec 30, 2020
fb37d6c
Merge branch 'mclag_enhacements' of https://github.com/gitsabari/soni…
gitsabari Dec 30, 2020
bfac892
UT Fix unique IP configuration
tapashdas Jan 8, 2021
7d3e270
Merge branch 'master' into mclag_enhacements
gitsabari Jan 12, 2021
325b50b
modified ip address validate function for mclag config verication
gitsabari Jan 12, 2021
004017c
Merge branch 'master' into mclag_enhacements
gitsabari Mar 2, 2021
b2b78c2
Add soft-reboot reboot type (#1453)
sujinmkang Mar 2, 2021
cd0ea5c
[warm-reboot] Check if warm restart flag is set when issuing a warm-r…
shi-su Mar 3, 2021
ed01209
Merge branch 'master' into mclag_enhacements
gitsabari Apr 5, 2021
357106f
Added mclag config commands
gitsabari Apr 5, 2021
ec65ed8
removed unwanted imports
gitsabari Apr 5, 2021
eac5461
added mclag tests
gitsabari Apr 5, 2021
6d78d72
fixed build issue
gitsabari Apr 6, 2021
bfa6a7d
corrected mclag test
gitsabari Apr 6, 2021
77f9ad4
corrected mclag test
gitsabari Apr 6, 2021
1292a5f
corrected mclag test case
gitsabari Apr 7, 2021
20305c5
Merge branch 'mclag_enhacements' of https://github.com/gitsabari/soni…
gitsabari Apr 7, 2021
e0c2233
updated testcase for mclag
gitsabari Apr 7, 2021
ba99b19
updated mclag config
gitsabari Apr 7, 2021
2bbfbc6
updated mclag test cases
gitsabari Apr 7, 2021
0ccd63f
updated mclag test case
gitsabari Apr 7, 2021
c119b40
updated mclag test cases
gitsabari Apr 7, 2021
5069580
fixed alert
gitsabari Apr 7, 2021
ea8db74
updated mclag test cases
gitsabari Apr 8, 2021
42cd9e1
updated mclag test cases
gitsabari May 4, 2021
2ed541d
updated mclag config
gitsabari May 4, 2021
03292e8
modified mclag test cases
gitsabari May 4, 2021
b71f614
updated mclag test case
gitsabari May 4, 2021
a8d28ef
updated mclag test case
gitsabari May 4, 2021
24ddd8d
updated mclag test cases
gitsabari May 4, 2021
685d6f5
updated mclag test cases
gitsabari May 4, 2021
6eade6b
updated mclag test cases
gitsabari May 4, 2021
91c8d46
updated mclag test case
gitsabari May 17, 2021
12938be
updated mclag test case
gitsabari May 18, 2021
29c90a0
updated mclag test cases
gitsabari May 18, 2021
b9a590c
updated mclag test cases
gitsabari May 18, 2021
7628b1b
updated mclag test cases
gitsabari May 18, 2021
3b38f8b
updated mclag test cases
gitsabari Jun 1, 2021
b468b81
updated mclag test case
gitsabari Jun 15, 2021
80bdcfc
updated mclag test cases
gitsabari Jun 15, 2021
0604e90
updated mclag test cases
gitsabari Jun 26, 2021
cbd88df
updated mclag test cases
gitsabari Jun 26, 2021
a3c2381
updated mclag test cases
gitsabari Jun 26, 2021
605538e
updated mclag test cases
gitsabari Jun 26, 2021
968094b
updated mclag test cases
gitsabari Jun 26, 2021
79493dc
updated mclag test cases
gitsabari Jun 26, 2021
1abcced
updated mclag test cases
gitsabari Jun 26, 2021
a98a968
updated mclag test case
gitsabari Jun 26, 2021
64dae65
updated mclag test cases
gitsabari Jun 27, 2021
5b19144
updated mclag test case
gitsabari Jun 27, 2021
791d806
updated mclag config to use swsscommon instead of swssdk
gitsabari Jul 10, 2021
36bb626
updated mclag config to use swsscommon
gitsabari Jul 10, 2021
8e76e2f
updated mclag config script file
gitsabari Jul 12, 2021
147f31c
fixed mclag test cases to verify config db
gitsabari Jul 13, 2021
1848e02
updated mclag test case with config db verify function
gitsabari Jul 13, 2021
6f8c74f
fixed build issue
gitsabari Jul 14, 2021
e376034
fixed build issue
gitsabari Jul 14, 2021
a4c0a85
fixed build issue
gitsabari Jul 14, 2021
26fd979
fixed build issue
gitsabari Jul 14, 2021
ba901c6
fixed build issue
gitsabari Jul 14, 2021
c47e5de
fixed build issue
gitsabari Jul 14, 2021
489c5b3
fixed build issue
gitsabari Jul 14, 2021
d5db6f8
updated test case
gitsabari Jul 14, 2021
59bb702
updatrd mclag test case
gitsabari Jul 14, 2021
a506c73
updated mclag test case
gitsabari Jul 14, 2021
de8ba45
updated mclag test case
gitsabari Jul 14, 2021
7c27d18
updated mclag test case
gitsabari Jul 14, 2021
95293db
updated mclag test case
gitsabari Jul 14, 2021
379b60e
updated mclag test case
gitsabari Jul 14, 2021
f95967c
addressed review comments
gitsabari Jul 16, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from . import vxlan
from . import plugins
from .config_mgmt import ConfigMgmtDPB
from . import mclag

# mock masic APIs for unit test
try:
Expand Down Expand Up @@ -876,6 +877,11 @@ def config(ctx):
config.add_command(vlan.vlan)
config.add_command(vxlan.vxlan)

#add mclag commands
config.add_command(mclag.mclag)
config.add_command(mclag.mclag_member)
config.add_command(mclag.mclag_unique_ip)

@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
348 changes: 348 additions & 0 deletions config/mclag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,348 @@
#!/usr/sbin/env python
gitsabari marked this conversation as resolved.
Show resolved Hide resolved

import click
from swsscommon.swsscommon import ConfigDBConnector
import ipaddress

CFG_PORTCHANNEL_PREFIX = "PortChannel"
CFG_PORTCHANNEL_PREFIX_LEN = 11
CFG_PORTCHANNEL_MAX_VAL = 9999
CFG_PORTCHANNEL_NAME_TOTAL_LEN_MAX = 15
CFG_PORTCHANNEL_NO="<0-9999>"

def mclag_domain_id_valid(domain_id):
"""Check if the domain id is in acceptable range (between 1 and 4095)
"""

if domain_id<1 or domain_id>4095:
return False

return True

def mclag_ka_session_dep_check(ka, session_tmout):
"""Check if the MCLAG Keepalive timer and session timeout values are multiples of each other and keepalive is < session timeout value
"""
if not session_tmout >= ( 3 * ka):
return False, "MCLAG Keepalive:{} Session_timeout:{} values not satisfying session_timeout >= (3 * KA) ".format(session_tmout, ka)

if session_tmout % ka:
return False, "MCLAG keepalive:{} Session_timeout:{} Values not satisfying session_timeout should be a multiple of KA".format(ka, session_tmout)

return True, ""


def mclag_ka_interval_valid(ka):
"""Check if the MCLAG Keepalive timer is in acceptable range (between 1 and 60)
"""
if ka < 1 or ka > 60:
return False, "Keepalive %s not in valid range[1-60]" % ka
return True, ""

def mclag_session_timeout_valid(session_tmout):
"""Check if the MCLAG session timeout in valid range (between 3 and 3600)
"""
if session_tmout < 3 or session_tmout > 3600:
return False, "Session timeout %s not in valid range[3-3600]" % session_tmout
return True, ""


def is_portchannel_name_valid(portchannel_name):
"""Port channel name validation
"""
# Return True if Portchannel name is PortChannelXXXX (XXXX can be 0-9999)
if portchannel_name[:CFG_PORTCHANNEL_PREFIX_LEN] != CFG_PORTCHANNEL_PREFIX :
return False
if (portchannel_name[CFG_PORTCHANNEL_PREFIX_LEN:].isdigit() is False or
int(portchannel_name[CFG_PORTCHANNEL_PREFIX_LEN:]) > CFG_PORTCHANNEL_MAX_VAL) :
return False
if len(portchannel_name) > CFG_PORTCHANNEL_NAME_TOTAL_LEN_MAX:
return False
return True

def is_ipv4_addr_valid(addr):
v4_invalid_list = [ipaddress.IPv4Address(str('0.0.0.0')), ipaddress.IPv4Address(str('255.255.255.255'))]
try:
ip = ipaddress.ip_address(str(addr))
if (ip.version == 4):
if (ip.is_reserved):
click.echo ("{} Not Valid, Reason: IPv4 reserved address range.".format(addr))
return False
elif (ip.is_multicast):
click.echo ("{} Not Valid, Reason: IPv4 Multicast address range.".format(addr))
return False
elif (ip in v4_invalid_list):
click.echo ("{} Not Valid.".format(addr))
return False
else:
return True

else:
click.echo ("{} Not Valid, Reason: Not an IPv4 address".format(addr))
return False

except ValueError:
return False



def check_if_interface_is_valid(db, interface_name):
from main import interface_name_is_valid
if interface_name_is_valid(db,interface_name) is False:
ctx.fail("Interface name is invalid. Please enter a valid interface name!!")

def get_intf_vrf_bind_unique_ip(db, interface_name, interface_type):
intfvrf = db.get_table(interface_type)
if interface_name in intfvrf:
if 'vrf_name' in intfvrf[interface_name]:
return intfvrf[interface_name]['vrf_name']
else:
return ""
else:
return ""


######
#
# 'mclag' group ('config mclag ...')
#
@click.group()
@click.pass_context
def mclag(ctx):
config_db = ConfigDBConnector()
config_db.connect()
ctx.obj = {'db': config_db}


#mclag domain add
@mclag.command('add')
@click.argument('domain_id', metavar='<domain_id>', required=True, type=int)
@click.argument('source_ip_addr', metavar='<source_ip_addr>', required=True)
@click.argument('peer_ip_addr', metavar='<peer_ip_addr>', required=True)
@click.argument('peer_ifname', metavar='<peer_ifname>', required=False)
@click.pass_context
def add_mclag_domain(ctx, domain_id, source_ip_addr, peer_ip_addr, peer_ifname):
"""Add MCLAG Domain"""

if not mclag_domain_id_valid(domain_id):
ctx.fail("{} invalid domain ID, valid range is 1 to 4095".format(domain_id))
if not is_ipv4_addr_valid(source_ip_addr):
ctx.fail("{} invalid local ip address".format(source_ip_addr))
if not is_ipv4_addr_valid(peer_ip_addr):
ctx.fail("{} invalid peer ip address".format(peer_ip_addr))

db = ctx.obj['db']
fvs = {}
fvs['source_ip'] = str(source_ip_addr)
fvs['peer_ip'] = str(peer_ip_addr)
if peer_ifname is not None:
if (peer_ifname.startswith("Ethernet") is False) and (peer_ifname.startswith("PortChannel") is False):
ctx.fail("peer interface is invalid, should be Ethernet interface or portChannel !!")
if (peer_ifname.startswith("Ethernet") is True) and (check_if_interface_is_valid(db, peer_ifname) is False):
ctx.fail("peer Ethernet interface name is invalid. it is not present in port table of configDb!!")
if (peer_ifname.startswith("PortChannel")) and (is_portchannel_name_valid(peer_ifname) is False):
ctx.fail("peer PortChannel interface name is invalid !!")
fvs['peer_link'] = str(peer_ifname)
mclag_domain_keys = db.get_table('MCLAG_DOMAIN').keys()
if len(mclag_domain_keys) == 0:
db.set_entry('MCLAG_DOMAIN', domain_id, fvs)
else:
if domain_id in mclag_domain_keys:
db.mod_entry('MCLAG_DOMAIN', domain_id, fvs)
else:
ctx.fail("only one mclag Domain can be configured. Already one domain {} configured ".format(mclag_domain_keys[0]))


#mclag domain delete
#MCLAG Domain del involves deletion of associated MCLAG Ifaces also
@mclag.command('del')
@click.argument('domain_id', metavar='<domain_id>', required=True, type=int)
@click.pass_context
def del_mclag_domain(ctx, domain_id):
"""Delete MCLAG Domain"""

if not mclag_domain_id_valid(domain_id):
ctx.fail("{} invalid domain ID, valid range is 1 to 4095".format(domain_id))

db = ctx.obj['db']
entry = db.get_entry('MCLAG_DOMAIN', domain_id)
if entry is None:
ctx.fail("MCLAG Domain {} not configured ".format(domain_id))
return

click.echo("MCLAG Domain delete takes care of deleting all associated MCLAG Interfaces")

#get all MCLAG Interface associated with this domain and delete
interface_table_keys = db.get_table('MCLAG_INTERFACE').keys()

#delete associated mclag interfaces
for iface_domain_id, iface_name in interface_table_keys:
if (int(iface_domain_id) == domain_id):
db.set_entry('MCLAG_INTERFACE', (iface_domain_id, iface_name), None )

#delete mclag domain
db.set_entry('MCLAG_DOMAIN', domain_id, None)


#keepalive timeout config
@mclag.command('keepalive-interval')
@click.argument('domain_id', metavar='<domain_id>', required=True)
@click.argument('time_in_secs', metavar='<time_in_secs>', required=True, type=int)
@click.pass_context
def config_mclag_keepalive_timer(ctx, domain_id, time_in_secs):
"""Configure MCLAG Keepalive timer value in secs"""
db = ctx.obj['db']

entry = db.get_entry('MCLAG_DOMAIN', domain_id)
if len(entry) == 0:
ctx.fail("MCLAG Domain " + domain_id + " not configured, configure mclag domain first")

status, error_info = mclag_ka_interval_valid(time_in_secs)
if status is not True:
ctx.fail(error_info)

session_timeout_value = entry.get('session_timeout')

if session_timeout_value is None:
# assign default value
int_sess_tmout = 15
else:
int_sess_tmout = int(session_timeout_value)

status, error_info = mclag_ka_session_dep_check(time_in_secs, int_sess_tmout)
if status is not True:
ctx.fail(error_info)

fvs = {}
fvs['keepalive_interval'] = str(time_in_secs)
db.mod_entry('MCLAG_DOMAIN', domain_id, fvs)


#session timeout config
@mclag.command('session-timeout')
@click.argument('domain_id', metavar='<domain_id>', required=True)
@click.argument('time_in_secs', metavar='<time_in_secs>', required=True, type=int)
@click.pass_context
def config_mclag_session_timeout(ctx, domain_id, time_in_secs):
"""Configure MCLAG Session timeout value in secs"""
db = ctx.obj['db']
entry = db.get_entry('MCLAG_DOMAIN', domain_id)
if len(entry) == 0:
ctx.fail("MCLAG Domain " + domain_id + " not configured, configure mclag domain first")

status, error_info = mclag_session_timeout_valid(time_in_secs)
if status is not True:
ctx.fail(error_info)

ka = entry.get('keepalive_interval')
if ka is None:
# assign default value
int_ka = 1
else:
int_ka = int(ka)

status, error_info = mclag_ka_session_dep_check(int_ka, time_in_secs)
if status is not True:
ctx.fail(error_info)

fvs = {}
fvs['session_timeout'] = str(time_in_secs)
db.mod_entry('MCLAG_DOMAIN', domain_id, fvs)


#mclag interface config
@mclag.group('member')
@click.pass_context
def mclag_member(ctx):
pass

@mclag_member.command('add')
@click.argument('domain_id', metavar='<domain_id>', required=True)
@click.argument('portchannel_names', metavar='<portchannel_names>', required=True)
@click.pass_context
def add_mclag_member(ctx, domain_id, portchannel_names):
"""Add member MCLAG interfaces from MCLAG Domain"""
db = ctx.obj['db']
entry = db.get_entry('MCLAG_DOMAIN', domain_id)
if len(entry) == 0:
ctx.fail("MCLAG Domain " + domain_id + " not configured, configure mclag domain first")

portchannel_list = portchannel_names.split(",")
for portchannel_name in portchannel_list:
if is_portchannel_name_valid(portchannel_name) != True:
ctx.fail("{} is invalid!, name should have prefix '{}' and suffix '{}'" .format(portchannel_name, CFG_PORTCHANNEL_PREFIX, CFG_PORTCHANNEL_NO))
db.set_entry('MCLAG_INTERFACE', (domain_id, portchannel_name), {'if_type':"PortChannel"} )

@mclag_member.command('del')
@click.argument('domain_id', metavar='<domain_id>', required=True)
@click.argument('portchannel_names', metavar='<portchannel_names>', required=True)
@click.pass_context
def del_mclag_member(ctx, domain_id, portchannel_names):
"""Delete member MCLAG interfaces from MCLAG Domain"""
db = ctx.obj['db']
#split comma seperated portchannel names
portchannel_list = portchannel_names.split(",")
for portchannel_name in portchannel_list:
if is_portchannel_name_valid(portchannel_name) != True:
ctx.fail("{} is invalid!, name should have prefix '{}' and suffix '{}'" .format(portchannel_name, CFG_PORTCHANNEL_PREFIX, CFG_PORTCHANNEL_NO))
db.set_entry('MCLAG_INTERFACE', (domain_id, portchannel_name), None )

#mclag unique ip config
@mclag.group('unique-ip')
@click.pass_context
def mclag_unique_ip(ctx):
"""Configure Unique IP on MCLAG Vlan interface"""
pass

@mclag_unique_ip.command('add')
@click.argument('interface_names', metavar='<interface_names>', required=True)
@click.pass_context
def add_mclag_unique_ip(ctx, interface_names):
"""Add Unique IP on MCLAG Vlan interface"""
db = ctx.obj['db']
mclag_domain_keys = db.get_table('MCLAG_DOMAIN').keys()
if len(mclag_domain_keys) == 0:
ctx.fail("MCLAG not configured. MCLAG should be configured.")

#split comma seperated interface names
interface_list = interface_names.split(",")
for interface_name in interface_list:
if not interface_name.startswith("Vlan"):
ctx.fail("{} is invalid!, name should have prefix '{}' and suffix '{}'" .format(interface_name, "Vlan", "vlan id"))
#VRF should be configured after unique IP configuration
intf_vrf = get_intf_vrf_bind_unique_ip(db, interface_name, "VLAN_INTERFACE")
if intf_vrf:
ctx.fail("%s is configured with Non default VRF, remove the VRF configuration and reconfigure after enabling unique IP configuration."%(str(interface_name)))

#IP should be configured after unique IP configuration
for k,v in db.get_table('VLAN_INTERFACE').items():
if type(k) == tuple:
(intf_name, ip) = k
if intf_name == interface_name and ip != 0:
ctx.fail("%s is configured with IP %s, remove the IP configuration and reconfigure after enabling unique IP configuration."%(str(intf_name), str(ip)))
db.set_entry('MCLAG_UNIQUE_IP', (interface_name), {'unique_ip':"enable"} )

@mclag_unique_ip.command('del')
@click.argument('interface_names', metavar='<interface_names>', required=True)
@click.pass_context
def del_mclag_unique_ip(ctx, interface_names):
"""Delete Unique IP from MCLAG Vlan interface"""
db = ctx.obj['db']
#split comma seperated interface names
interface_list = interface_names.split(",")
for interface_name in interface_list:
if not interface_name.startswith("Vlan"):
ctx.fail("{} is invalid!, name should have prefix '{}' and suffix '{}'" .format(interface_name, "Vlan", "vlan id"))
#VRF should be configured after removing unique IP configuration
intf_vrf = get_intf_vrf_bind_unique_ip(db, interface_name, "VLAN_INTERFACE")
if intf_vrf:
ctx.fail("%s is configured with Non default VRF, remove the VRF configuration and reconfigure after disabling unique IP configuration."%(str(interface_name)))
#IP should be configured after removing unique IP configuration
for k,v in db.get_table('VLAN_INTERFACE').items():
if type(k) == tuple:
(intf_name, ip) = k
if intf_name == interface_name and ip != 0:
ctx.fail("%s is configured with IP %s, remove the IP configuration and reconfigure after disabling unique IP configuration."%(str(intf_name), str(ip)))
db.set_entry('MCLAG_UNIQUE_IP', (interface_name), None )

#######

Loading