-
Notifications
You must be signed in to change notification settings - Fork 662
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
mclag enhancements as per HLD at sonic-net/SONiC#596 (#1138)
* mclagsyncd enhancements as per HLD at sonic-net/SONiC#596 * addressed LGTM alert * UT Fix unique IP configuration * modified ip address validate function for mclag config verication * Add soft-reboot reboot type (#1453) What I did Add a new reboot named as soft-reboot which can be performed by "kexec -e" How I did it Replace the platform reboot with "kexec -e" for the cold reboot case. How to verify it Verified the reboot on DUT and check the reboot-cause * [warm-reboot] Check if warm restart flag is set when issuing a warm-reboot (#1460) Check if any warm restart flag is set when issuing a warm-reboot. This check avoids starting a warm reboot while another warm restart is in progress. In the scenario where a warm reboot is issued with another warm restart in progress, the warm restart flag may be reset and part of the components have a risk of doing cold reboot. * Added mclag config commands * removed unwanted imports * added mclag tests * fixed build issue * corrected mclag test * corrected mclag test * corrected mclag test case * updated testcase for mclag * updated mclag config * updated mclag test cases * updated mclag test case * updated mclag test cases * fixed alert * updated mclag test cases * updated mclag test cases * updated mclag config * modified mclag test cases * updated mclag test case * updated mclag test case * updated mclag test cases * updated mclag test cases * updated mclag test cases * updated mclag test case * updated mclag test case * updated mclag test cases * updated mclag test cases * updated mclag test cases * updated mclag test cases * updated mclag test case * updated mclag test cases * updated mclag test cases * updated mclag test cases * updated mclag test cases * updated mclag test cases * updated mclag test cases * updated mclag test cases * updated mclag test cases * updated mclag test case * updated mclag test cases * updated mclag test case * updated mclag config to use swsscommon instead of swssdk * updated mclag config to use swsscommon * updated mclag config script file * fixed mclag test cases to verify config db * updated mclag test case with config db verify function * fixed build issue * updated test case * updated mclag test case * addressed review comments Co-authored-by: Tapash Das <[email protected]> Co-authored-by: Tapash Das <[email protected]> Co-authored-by: Sujin Kang <[email protected]> Co-authored-by: Shi Su <[email protected]>
- Loading branch information
1 parent
e98bbb6
commit 0efd297
Showing
3 changed files
with
994 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,347 @@ | ||
|
||
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 ) | ||
|
||
####### | ||
|
Oops, something went wrong.