Skip to content

Commit 0efd297

Browse files
gitsabaritapashdassujinmkangshi-su
authored
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 <tapash.das@broadcom.com> Co-authored-by: Tapash Das <48195098+tapashdas@users.noreply.github.com> Co-authored-by: Sujin Kang <sujkang@microsoft.com> Co-authored-by: Shi Su <67605788+shi-su@users.noreply.github.com>
1 parent e98bbb6 commit 0efd297

File tree

3 files changed

+994
-0
lines changed

3 files changed

+994
-0
lines changed

config/main.py

+6
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from . import vxlan
4242
from . import plugins
4343
from .config_mgmt import ConfigMgmtDPB
44+
from . import mclag
4445

4546
# mock masic APIs for unit test
4647
try:
@@ -978,6 +979,11 @@ def config(ctx):
978979
config.add_command(vlan.vlan)
979980
config.add_command(vxlan.vxlan)
980981

982+
#add mclag commands
983+
config.add_command(mclag.mclag)
984+
config.add_command(mclag.mclag_member)
985+
config.add_command(mclag.mclag_unique_ip)
986+
981987
@config.command()
982988
@click.option('-y', '--yes', is_flag=True, callback=_abort_if_false,
983989
expose_value=False, prompt='Existing files will be overwritten, continue?')

config/mclag.py

+347
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
2+
import click
3+
from swsscommon.swsscommon import ConfigDBConnector
4+
import ipaddress
5+
6+
CFG_PORTCHANNEL_PREFIX = "PortChannel"
7+
CFG_PORTCHANNEL_PREFIX_LEN = 11
8+
CFG_PORTCHANNEL_MAX_VAL = 9999
9+
CFG_PORTCHANNEL_NAME_TOTAL_LEN_MAX = 15
10+
CFG_PORTCHANNEL_NO="<0-9999>"
11+
12+
def mclag_domain_id_valid(domain_id):
13+
"""Check if the domain id is in acceptable range (between 1 and 4095)
14+
"""
15+
16+
if domain_id<1 or domain_id>4095:
17+
return False
18+
19+
return True
20+
21+
def mclag_ka_session_dep_check(ka, session_tmout):
22+
"""Check if the MCLAG Keepalive timer and session timeout values are multiples of each other and keepalive is < session timeout value
23+
"""
24+
if not session_tmout >= ( 3 * ka):
25+
return False, "MCLAG Keepalive:{} Session_timeout:{} values not satisfying session_timeout >= (3 * KA) ".format(session_tmout, ka)
26+
27+
if session_tmout % ka:
28+
return False, "MCLAG keepalive:{} Session_timeout:{} Values not satisfying session_timeout should be a multiple of KA".format(ka, session_tmout)
29+
30+
return True, ""
31+
32+
33+
def mclag_ka_interval_valid(ka):
34+
"""Check if the MCLAG Keepalive timer is in acceptable range (between 1 and 60)
35+
"""
36+
if ka < 1 or ka > 60:
37+
return False, "Keepalive %s not in valid range[1-60]" % ka
38+
return True, ""
39+
40+
def mclag_session_timeout_valid(session_tmout):
41+
"""Check if the MCLAG session timeout in valid range (between 3 and 3600)
42+
"""
43+
if session_tmout < 3 or session_tmout > 3600:
44+
return False, "Session timeout %s not in valid range[3-3600]" % session_tmout
45+
return True, ""
46+
47+
48+
def is_portchannel_name_valid(portchannel_name):
49+
"""Port channel name validation
50+
"""
51+
# Return True if Portchannel name is PortChannelXXXX (XXXX can be 0-9999)
52+
if portchannel_name[:CFG_PORTCHANNEL_PREFIX_LEN] != CFG_PORTCHANNEL_PREFIX :
53+
return False
54+
if (portchannel_name[CFG_PORTCHANNEL_PREFIX_LEN:].isdigit() is False or
55+
int(portchannel_name[CFG_PORTCHANNEL_PREFIX_LEN:]) > CFG_PORTCHANNEL_MAX_VAL) :
56+
return False
57+
if len(portchannel_name) > CFG_PORTCHANNEL_NAME_TOTAL_LEN_MAX:
58+
return False
59+
return True
60+
61+
def is_ipv4_addr_valid(addr):
62+
v4_invalid_list = [ipaddress.IPv4Address(str('0.0.0.0')), ipaddress.IPv4Address(str('255.255.255.255'))]
63+
try:
64+
ip = ipaddress.ip_address(str(addr))
65+
if (ip.version == 4):
66+
if (ip.is_reserved):
67+
click.echo ("{} Not Valid, Reason: IPv4 reserved address range.".format(addr))
68+
return False
69+
elif (ip.is_multicast):
70+
click.echo ("{} Not Valid, Reason: IPv4 Multicast address range.".format(addr))
71+
return False
72+
elif (ip in v4_invalid_list):
73+
click.echo ("{} Not Valid.".format(addr))
74+
return False
75+
else:
76+
return True
77+
78+
else:
79+
click.echo ("{} Not Valid, Reason: Not an IPv4 address".format(addr))
80+
return False
81+
82+
except ValueError:
83+
return False
84+
85+
86+
87+
def check_if_interface_is_valid(db, interface_name):
88+
from main import interface_name_is_valid
89+
if interface_name_is_valid(db,interface_name) is False:
90+
ctx.fail("Interface name is invalid. Please enter a valid interface name!!")
91+
92+
def get_intf_vrf_bind_unique_ip(db, interface_name, interface_type):
93+
intfvrf = db.get_table(interface_type)
94+
if interface_name in intfvrf:
95+
if 'vrf_name' in intfvrf[interface_name]:
96+
return intfvrf[interface_name]['vrf_name']
97+
else:
98+
return ""
99+
else:
100+
return ""
101+
102+
103+
######
104+
#
105+
# 'mclag' group ('config mclag ...')
106+
#
107+
@click.group()
108+
@click.pass_context
109+
def mclag(ctx):
110+
config_db = ConfigDBConnector()
111+
config_db.connect()
112+
ctx.obj = {'db': config_db}
113+
114+
115+
#mclag domain add
116+
@mclag.command('add')
117+
@click.argument('domain_id', metavar='<domain_id>', required=True, type=int)
118+
@click.argument('source_ip_addr', metavar='<source_ip_addr>', required=True)
119+
@click.argument('peer_ip_addr', metavar='<peer_ip_addr>', required=True)
120+
@click.argument('peer_ifname', metavar='<peer_ifname>', required=False)
121+
@click.pass_context
122+
def add_mclag_domain(ctx, domain_id, source_ip_addr, peer_ip_addr, peer_ifname):
123+
"""Add MCLAG Domain"""
124+
125+
if not mclag_domain_id_valid(domain_id):
126+
ctx.fail("{} invalid domain ID, valid range is 1 to 4095".format(domain_id))
127+
if not is_ipv4_addr_valid(source_ip_addr):
128+
ctx.fail("{} invalid local ip address".format(source_ip_addr))
129+
if not is_ipv4_addr_valid(peer_ip_addr):
130+
ctx.fail("{} invalid peer ip address".format(peer_ip_addr))
131+
132+
db = ctx.obj['db']
133+
fvs = {}
134+
fvs['source_ip'] = str(source_ip_addr)
135+
fvs['peer_ip'] = str(peer_ip_addr)
136+
if peer_ifname is not None:
137+
if (peer_ifname.startswith("Ethernet") is False) and (peer_ifname.startswith("PortChannel") is False):
138+
ctx.fail("peer interface is invalid, should be Ethernet interface or portChannel !!")
139+
if (peer_ifname.startswith("Ethernet") is True) and (check_if_interface_is_valid(db, peer_ifname) is False):
140+
ctx.fail("peer Ethernet interface name is invalid. it is not present in port table of configDb!!")
141+
if (peer_ifname.startswith("PortChannel")) and (is_portchannel_name_valid(peer_ifname) is False):
142+
ctx.fail("peer PortChannel interface name is invalid !!")
143+
fvs['peer_link'] = str(peer_ifname)
144+
mclag_domain_keys = db.get_table('MCLAG_DOMAIN').keys()
145+
if len(mclag_domain_keys) == 0:
146+
db.set_entry('MCLAG_DOMAIN', domain_id, fvs)
147+
else:
148+
if domain_id in mclag_domain_keys:
149+
db.mod_entry('MCLAG_DOMAIN', domain_id, fvs)
150+
else:
151+
ctx.fail("only one mclag Domain can be configured. Already one domain {} configured ".format(mclag_domain_keys[0]))
152+
153+
154+
#mclag domain delete
155+
#MCLAG Domain del involves deletion of associated MCLAG Ifaces also
156+
@mclag.command('del')
157+
@click.argument('domain_id', metavar='<domain_id>', required=True, type=int)
158+
@click.pass_context
159+
def del_mclag_domain(ctx, domain_id):
160+
"""Delete MCLAG Domain"""
161+
162+
if not mclag_domain_id_valid(domain_id):
163+
ctx.fail("{} invalid domain ID, valid range is 1 to 4095".format(domain_id))
164+
165+
db = ctx.obj['db']
166+
entry = db.get_entry('MCLAG_DOMAIN', domain_id)
167+
if entry is None:
168+
ctx.fail("MCLAG Domain {} not configured ".format(domain_id))
169+
return
170+
171+
click.echo("MCLAG Domain delete takes care of deleting all associated MCLAG Interfaces")
172+
173+
#get all MCLAG Interface associated with this domain and delete
174+
interface_table_keys = db.get_table('MCLAG_INTERFACE').keys()
175+
176+
#delete associated mclag interfaces
177+
for iface_domain_id, iface_name in interface_table_keys:
178+
if (int(iface_domain_id) == domain_id):
179+
db.set_entry('MCLAG_INTERFACE', (iface_domain_id, iface_name), None )
180+
181+
#delete mclag domain
182+
db.set_entry('MCLAG_DOMAIN', domain_id, None)
183+
184+
185+
#keepalive timeout config
186+
@mclag.command('keepalive-interval')
187+
@click.argument('domain_id', metavar='<domain_id>', required=True)
188+
@click.argument('time_in_secs', metavar='<time_in_secs>', required=True, type=int)
189+
@click.pass_context
190+
def config_mclag_keepalive_timer(ctx, domain_id, time_in_secs):
191+
"""Configure MCLAG Keepalive timer value in secs"""
192+
db = ctx.obj['db']
193+
194+
entry = db.get_entry('MCLAG_DOMAIN', domain_id)
195+
if len(entry) == 0:
196+
ctx.fail("MCLAG Domain " + domain_id + " not configured, configure mclag domain first")
197+
198+
status, error_info = mclag_ka_interval_valid(time_in_secs)
199+
if status is not True:
200+
ctx.fail(error_info)
201+
202+
session_timeout_value = entry.get('session_timeout')
203+
204+
if session_timeout_value is None:
205+
# assign default value
206+
int_sess_tmout = 15
207+
else:
208+
int_sess_tmout = int(session_timeout_value)
209+
210+
status, error_info = mclag_ka_session_dep_check(time_in_secs, int_sess_tmout)
211+
if status is not True:
212+
ctx.fail(error_info)
213+
214+
fvs = {}
215+
fvs['keepalive_interval'] = str(time_in_secs)
216+
db.mod_entry('MCLAG_DOMAIN', domain_id, fvs)
217+
218+
219+
#session timeout config
220+
@mclag.command('session-timeout')
221+
@click.argument('domain_id', metavar='<domain_id>', required=True)
222+
@click.argument('time_in_secs', metavar='<time_in_secs>', required=True, type=int)
223+
@click.pass_context
224+
def config_mclag_session_timeout(ctx, domain_id, time_in_secs):
225+
"""Configure MCLAG Session timeout value in secs"""
226+
db = ctx.obj['db']
227+
entry = db.get_entry('MCLAG_DOMAIN', domain_id)
228+
if len(entry) == 0:
229+
ctx.fail("MCLAG Domain " + domain_id + " not configured, configure mclag domain first")
230+
231+
status, error_info = mclag_session_timeout_valid(time_in_secs)
232+
if status is not True:
233+
ctx.fail(error_info)
234+
235+
ka = entry.get('keepalive_interval')
236+
if ka is None:
237+
# assign default value
238+
int_ka = 1
239+
else:
240+
int_ka = int(ka)
241+
242+
status, error_info = mclag_ka_session_dep_check(int_ka, time_in_secs)
243+
if status is not True:
244+
ctx.fail(error_info)
245+
246+
fvs = {}
247+
fvs['session_timeout'] = str(time_in_secs)
248+
db.mod_entry('MCLAG_DOMAIN', domain_id, fvs)
249+
250+
251+
#mclag interface config
252+
@mclag.group('member')
253+
@click.pass_context
254+
def mclag_member(ctx):
255+
pass
256+
257+
@mclag_member.command('add')
258+
@click.argument('domain_id', metavar='<domain_id>', required=True)
259+
@click.argument('portchannel_names', metavar='<portchannel_names>', required=True)
260+
@click.pass_context
261+
def add_mclag_member(ctx, domain_id, portchannel_names):
262+
"""Add member MCLAG interfaces from MCLAG Domain"""
263+
db = ctx.obj['db']
264+
entry = db.get_entry('MCLAG_DOMAIN', domain_id)
265+
if len(entry) == 0:
266+
ctx.fail("MCLAG Domain " + domain_id + " not configured, configure mclag domain first")
267+
268+
portchannel_list = portchannel_names.split(",")
269+
for portchannel_name in portchannel_list:
270+
if is_portchannel_name_valid(portchannel_name) != True:
271+
ctx.fail("{} is invalid!, name should have prefix '{}' and suffix '{}'" .format(portchannel_name, CFG_PORTCHANNEL_PREFIX, CFG_PORTCHANNEL_NO))
272+
db.set_entry('MCLAG_INTERFACE', (domain_id, portchannel_name), {'if_type':"PortChannel"} )
273+
274+
@mclag_member.command('del')
275+
@click.argument('domain_id', metavar='<domain_id>', required=True)
276+
@click.argument('portchannel_names', metavar='<portchannel_names>', required=True)
277+
@click.pass_context
278+
def del_mclag_member(ctx, domain_id, portchannel_names):
279+
"""Delete member MCLAG interfaces from MCLAG Domain"""
280+
db = ctx.obj['db']
281+
#split comma seperated portchannel names
282+
portchannel_list = portchannel_names.split(",")
283+
for portchannel_name in portchannel_list:
284+
if is_portchannel_name_valid(portchannel_name) != True:
285+
ctx.fail("{} is invalid!, name should have prefix '{}' and suffix '{}'" .format(portchannel_name, CFG_PORTCHANNEL_PREFIX, CFG_PORTCHANNEL_NO))
286+
db.set_entry('MCLAG_INTERFACE', (domain_id, portchannel_name), None )
287+
288+
#mclag unique ip config
289+
@mclag.group('unique-ip')
290+
@click.pass_context
291+
def mclag_unique_ip(ctx):
292+
"""Configure Unique IP on MCLAG Vlan interface"""
293+
pass
294+
295+
@mclag_unique_ip.command('add')
296+
@click.argument('interface_names', metavar='<interface_names>', required=True)
297+
@click.pass_context
298+
def add_mclag_unique_ip(ctx, interface_names):
299+
"""Add Unique IP on MCLAG Vlan interface"""
300+
db = ctx.obj['db']
301+
mclag_domain_keys = db.get_table('MCLAG_DOMAIN').keys()
302+
if len(mclag_domain_keys) == 0:
303+
ctx.fail("MCLAG not configured. MCLAG should be configured.")
304+
305+
#split comma seperated interface names
306+
interface_list = interface_names.split(",")
307+
for interface_name in interface_list:
308+
if not interface_name.startswith("Vlan"):
309+
ctx.fail("{} is invalid!, name should have prefix '{}' and suffix '{}'" .format(interface_name, "Vlan", "vlan id"))
310+
#VRF should be configured after unique IP configuration
311+
intf_vrf = get_intf_vrf_bind_unique_ip(db, interface_name, "VLAN_INTERFACE")
312+
if intf_vrf:
313+
ctx.fail("%s is configured with Non default VRF, remove the VRF configuration and reconfigure after enabling unique IP configuration."%(str(interface_name)))
314+
315+
#IP should be configured after unique IP configuration
316+
for k,v in db.get_table('VLAN_INTERFACE').items():
317+
if type(k) == tuple:
318+
(intf_name, ip) = k
319+
if intf_name == interface_name and ip != 0:
320+
ctx.fail("%s is configured with IP %s, remove the IP configuration and reconfigure after enabling unique IP configuration."%(str(intf_name), str(ip)))
321+
db.set_entry('MCLAG_UNIQUE_IP', (interface_name), {'unique_ip':"enable"} )
322+
323+
@mclag_unique_ip.command('del')
324+
@click.argument('interface_names', metavar='<interface_names>', required=True)
325+
@click.pass_context
326+
def del_mclag_unique_ip(ctx, interface_names):
327+
"""Delete Unique IP from MCLAG Vlan interface"""
328+
db = ctx.obj['db']
329+
#split comma seperated interface names
330+
interface_list = interface_names.split(",")
331+
for interface_name in interface_list:
332+
if not interface_name.startswith("Vlan"):
333+
ctx.fail("{} is invalid!, name should have prefix '{}' and suffix '{}'" .format(interface_name, "Vlan", "vlan id"))
334+
#VRF should be configured after removing unique IP configuration
335+
intf_vrf = get_intf_vrf_bind_unique_ip(db, interface_name, "VLAN_INTERFACE")
336+
if intf_vrf:
337+
ctx.fail("%s is configured with Non default VRF, remove the VRF configuration and reconfigure after disabling unique IP configuration."%(str(interface_name)))
338+
#IP should be configured after removing unique IP configuration
339+
for k,v in db.get_table('VLAN_INTERFACE').items():
340+
if type(k) == tuple:
341+
(intf_name, ip) = k
342+
if intf_name == interface_name and ip != 0:
343+
ctx.fail("%s is configured with IP %s, remove the IP configuration and reconfigure after disabling unique IP configuration."%(str(intf_name), str(ip)))
344+
db.set_entry('MCLAG_UNIQUE_IP', (interface_name), None )
345+
346+
#######
347+

0 commit comments

Comments
 (0)