@@ -336,6 +336,43 @@ def interface_name_is_valid(config_db, interface_name):
336336 return True
337337 return False
338338
339+ def mclag_domain_id_valid (domain_id ):
340+ """Check if the domain id is in acceptable range (between 1 and 4095)
341+ """
342+
343+ if domain_id < 1 or domain_id > 4095 :
344+ return False
345+
346+ return True
347+
348+ def mclag_ka_session_dep_check (ka , session_tmout ):
349+ """Check if the MCLAG Keepalive timer and session timeout values are multiples of each other and keepalive is < session timeout value
350+ """
351+ if not session_tmout >= ( 3 * ka ):
352+ return False , "MCLAG Keepalive:{} Session_timeout:{} values not satisfying session_timeout >= (3 * KA) " .format (session_tmout , ka )
353+
354+ if session_tmout % ka :
355+ return False , "MCLAG keepalive:{} Session_timeout:{} Values not satisfying session_timeout should be a multiple of KA" .format (ka , session_tmout )
356+
357+ return True , ""
358+
359+
360+ def mclag_ka_interval_valid (ka ):
361+ """Check if the MCLAG Keepalive timer is in acceptable range (between 1 and 60)
362+ """
363+ if ka < 1 or ka > 60 :
364+ return False , "Keepalive %s not in valid range[1-60]" % ka
365+ return True , ""
366+
367+ def mclag_session_timeout_valid (session_tmout ):
368+ """Check if the MCLAG session timeout in valid range (between 3 and 3600)
369+ """
370+ if session_tmout < 3 or session_tmout > 3600 :
371+ return False , "Session timeout %s not in valid range[3-3600]" % session_tmout
372+ return True , ""
373+
374+
375+
339376def interface_name_to_alias (config_db , interface_name ):
340377 """Return alias interface name if default name is given as argument
341378 """
@@ -3668,6 +3705,247 @@ def delete(ctx):
36683705
36693706 sflow_tbl ['global' ].pop ('agent_id' )
36703707 config_db .set_entry ('SFLOW' , 'global' , sflow_tbl ['global' ])
3708+ ######
3709+ #
3710+ # 'mclag' group ('config mclag ...')
3711+ #
3712+ @config .group ()
3713+ @click .pass_context
3714+ def mclag (ctx ):
3715+ config_db = ConfigDBConnector ()
3716+ config_db .connect ()
3717+ ctx .obj = {'db' : config_db }
3718+ pass
3719+
3720+
3721+ #mclag domain add
3722+ @mclag .command ('add' )
3723+ @click .argument ('domain_id' , metavar = '<domain_id>' , required = True , type = int )
3724+ @click .argument ('source_ip_addr' , metavar = '<source_ip_addr>' , required = True )
3725+ @click .argument ('peer_ip_addr' , metavar = '<peer_ip_addr>' , required = True )
3726+ @click .argument ('peer_ifname' , metavar = '<peer_ifname>' , required = False )
3727+ @click .pass_context
3728+ def add_mclag_domain (ctx , domain_id , source_ip_addr , peer_ip_addr , peer_ifname ):
3729+ """Add MCLAG Domain"""
3730+
3731+ if not mclag_domain_id_valid (domain_id ):
3732+ ctx .fail ("{} invalid domain ID, valid range is 1 to 4095" .format (domain_id ))
3733+ if not is_ip4_addr_valid (source_ip_addr , True ):
3734+ ctx .fail ("{} invalid local ip address" .format (source_ip_addr ))
3735+ if not is_ip4_addr_valid (peer_ip_addr , True ):
3736+ ctx .fail ("{} invalid peer ip address" .format (peer_ip_addr ))
3737+
3738+ db = ctx .obj ['db' ]
3739+ fvs = {}
3740+ fvs ['source_ip' ] = str (source_ip_addr )
3741+ fvs ['peer_ip' ] = str (peer_ip_addr )
3742+ if peer_ifname is not None :
3743+ if (peer_ifname .startswith ("Ethernet" ) is False ) and (peer_ifname .startswith ("PortChannel" ) is False ):
3744+ ctx .fail ("peer interface is invalid, should be Ethernet interface or portChannel !!" )
3745+ if (peer_ifname .startswith ("Ethernet" ) is True ) and (interface_name_is_valid (peer_ifname ) is False ):
3746+ ctx .fail ("peer Ethernet interface name is invalid. it is not present in port table of configDb!!" )
3747+ if (peer_ifname .startswith ("PortChannel" )) and (is_portchannel_name_valid (peer_ifname ) is False ):
3748+ ctx .fail ("peer PortChannel interface name is invalid !!" )
3749+ fvs ['peer_link' ] = str (peer_ifname )
3750+ mclag_domain_keys = db .get_table ('MCLAG_DOMAIN' ).keys ()
3751+ if len (mclag_domain_keys ) == 0 :
3752+ db .set_entry ('MCLAG_DOMAIN' , domain_id , fvs )
3753+ else :
3754+ if domain_id in mclag_domain_keys :
3755+ db .mod_entry ('MCLAG_DOMAIN' , domain_id , fvs )
3756+ else :
3757+ ctx .fail ("only one mclag Domain can be configured. Already one domain {} configured " .format (mclag_domain_keys [0 ]))
3758+
3759+
3760+ #mclag domain delete
3761+ #MCLAG Domain del involves deletion of associated MCLAG Ifaces also
3762+ @mclag .command ('del' )
3763+ @click .argument ('domain_id' , metavar = '<domain_id>' , required = True , type = int )
3764+ @click .pass_context
3765+ def del_mclag_domain (ctx , domain_id ):
3766+ """Delete MCLAG Domain"""
3767+
3768+ if not mclag_domain_id_valid (domain_id ):
3769+ ctx .fail ("{} invalid domain ID, valid range is 1 to 4095" .format (domain_id ))
3770+
3771+ db = ctx .obj ['db' ]
3772+ entry = db .get_entry ('MCLAG_DOMAIN' , domain_id )
3773+ if entry is None :
3774+ ctx .fail ("MCLAG Domain {} not configured " .format (domain_id ))
3775+ return
3776+
3777+ click .echo ("MCLAG Domain delete takes care of deleting all associated MCLAG Interfaces" )
3778+
3779+ #get all MCLAG Interface associated with this domain and delete
3780+ interface_table_keys = db .get_table ('MCLAG_INTERFACE' ).keys ()
3781+
3782+ #delete associated mclag interfaces
3783+ for iface_domain_id , iface_name in interface_table_keys :
3784+ if (int (iface_domain_id ) == domain_id ):
3785+ db .set_entry ('MCLAG_INTERFACE' , (iface_domain_id , iface_name ), None )
3786+
3787+ #delete mclag domain
3788+ db .set_entry ('MCLAG_DOMAIN' , domain_id , None )
3789+
3790+
3791+ #keepalive timeout config
3792+ @mclag .command ('keepalive-interval' )
3793+ @click .argument ('domain_id' , metavar = '<domain_id>' , required = True )
3794+ @click .argument ('time_in_secs' , metavar = '<time_in_secs>' , required = True , type = int )
3795+ @click .pass_context
3796+ def config_mclag_keepalive_timer (ctx , domain_id , time_in_secs ):
3797+ """Configure MCLAG Keepalive timer value in secs"""
3798+ db = ctx .obj ['db' ]
3799+
3800+ entry = db .get_entry ('MCLAG_DOMAIN' , domain_id )
3801+ if len (entry ) == 0 :
3802+ ctx .fail ("MCLAG Domain " + domain_id + " not configured, configure mclag domain first" )
3803+
3804+ status , error_info = mclag_ka_interval_valid (time_in_secs )
3805+ if status is not True :
3806+ ctx .fail (error_info )
3807+
3808+ session_timeout_value = entry .get ('session_timeout' )
3809+
3810+ if session_timeout_value is None :
3811+ # assign default value
3812+ int_sess_tmout = 15
3813+ else :
3814+ int_sess_tmout = int (session_timeout_value )
3815+
3816+ status , error_info = mclag_ka_session_dep_check (time_in_secs , int_sess_tmout )
3817+ if status is not True :
3818+ ctx .fail (error_info )
3819+
3820+ fvs = {}
3821+ fvs ['keepalive_interval' ] = str (time_in_secs )
3822+ db .mod_entry ('MCLAG_DOMAIN' , domain_id , fvs )
3823+
3824+
3825+ #session timeout config
3826+ @mclag .command ('session-timeout' )
3827+ @click .argument ('domain_id' , metavar = '<domain_id>' , required = True )
3828+ @click .argument ('time_in_secs' , metavar = '<time_in_secs>' , required = True , type = int )
3829+ @click .pass_context
3830+ def config_mclag_session_timeout (ctx , domain_id , time_in_secs ):
3831+ """Configure MCLAG Session timeout value in secs"""
3832+ db = ctx .obj ['db' ]
3833+ entry = db .get_entry ('MCLAG_DOMAIN' , domain_id )
3834+ if len (entry ) == 0 :
3835+ ctx .fail ("MCLAG Domain " + domain_id + " not configured, configure mclag domain first" )
3836+
3837+ status , error_info = mclag_session_timeout_valid (time_in_secs )
3838+ if status is not True :
3839+ ctx .fail (error_info )
3840+
3841+ ka = entry .get ('keepalive_interval' )
3842+ if ka is None :
3843+ # assign default value
3844+ int_ka = 1
3845+ else :
3846+ int_ka = int (ka )
3847+
3848+ status , error_info = mclag_ka_session_dep_check (int_ka , time_in_secs )
3849+ if status is not True :
3850+ ctx .fail (error_info )
3851+
3852+ fvs = {}
3853+ fvs ['session_timeout' ] = str (time_in_secs )
3854+ db .mod_entry ('MCLAG_DOMAIN' , domain_id , fvs )
3855+
3856+
3857+ #mclag interface config
3858+ @mclag .group ('member' )
3859+ @click .pass_context
3860+ def mclag_member (ctx ):
3861+ pass
3862+
3863+ @mclag_member .command ('add' )
3864+ @click .argument ('domain_id' , metavar = '<domain_id>' , required = True )
3865+ @click .argument ('portchannel_names' , metavar = '<portchannel_names>' , required = True )
3866+ @click .pass_context
3867+ def add_mclag_member (ctx , domain_id , portchannel_names ):
3868+ """Add member MCLAG interfaces from MCLAG Domain"""
3869+ db = ctx .obj ['db' ]
3870+ portchannel_list = portchannel_names .split ("," )
3871+ for portchannel_name in portchannel_list :
3872+ if is_portchannel_name_valid (portchannel_name ) != True :
3873+ ctx .fail ("{} is invalid!, name should have prefix '{}' and suffix '{}'" .format (portchannel_name , CFG_PORTCHANNEL_PREFIX , CFG_PORTCHANNEL_NO ))
3874+ db .set_entry ('MCLAG_INTERFACE' , (domain_id , portchannel_name ), {'if_type' :"PortChannel" } )
3875+
3876+ @mclag_member .command ('del' )
3877+ @click .argument ('domain_id' , metavar = '<domain_id>' , required = True )
3878+ @click .argument ('portchannel_names' , metavar = '<portchannel_names>' , required = True )
3879+ @click .pass_context
3880+ def del_mclag_member (ctx , domain_id , portchannel_names ):
3881+ """Delete member MCLAG interfaces from MCLAG Domain"""
3882+ db = ctx .obj ['db' ]
3883+ #split comma seperated portchannel names
3884+ portchannel_list = portchannel_names .split ("," )
3885+ for portchannel_name in portchannel_list :
3886+ if is_portchannel_name_valid (portchannel_name ) != True :
3887+ ctx .fail ("{} is invalid!, name should have prefix '{}' and suffix '{}'" .format (portchannel_name , CFG_PORTCHANNEL_PREFIX , CFG_PORTCHANNEL_NO ))
3888+ db .set_entry ('MCLAG_INTERFACE' , (domain_id , portchannel_name ), None )
3889+
3890+ #mclag unique ip config
3891+ @mclag .group ('unique-ip' )
3892+ @click .pass_context
3893+ def mclag_unique_ip (ctx ):
3894+ """Add unique ip on MCLAG vlan interface"""
3895+ pass
3896+
3897+ @mclag_unique_ip .command ('add' )
3898+ @click .argument ('interface_names' , metavar = '<interface_names>' , required = True )
3899+ @click .pass_context
3900+ def add_mclag_unique_ip (ctx , interface_names ):
3901+ """Add unique ip on MCLAG vlan interface"""
3902+ db = ctx .obj ['db' ]
3903+ mclag_domain_keys = db .get_table ('MCLAG_DOMAIN' ).keys ()
3904+ if len (mclag_domain_keys ) == 0 :
3905+ ctx .fail ("MCLAG not configured. MCLAG should be configured." )
3906+
3907+ #split comma seperated interface names
3908+ interface_list = interface_names .split ("," )
3909+ for interface_name in interface_list :
3910+ if not interface_name .startswith ("Vlan" ):
3911+ ctx .fail ("{} is invalid!, name should have prefix '{}' and suffix '{}'" .format (interface_name , "Vlan" , "vlan id" ))
3912+ #VRF should be configured after unique IP configuration
3913+ intf_vrf = get_intf_vrf_bind_unique_ip (db , interface_name , "VLAN_INTERFACE" )
3914+ if intf_vrf :
3915+ ctx .fail ("%s is configured with Non default VRF, remove the VRF configuration and reconfigure after enabling unique IP configuration." % (str (interface_name )))
3916+
3917+ #IP should be configured after unique IP configuration
3918+ for k ,v in db .get_table ('VLAN_INTERFACE' ).iteritems ():
3919+ if type (k ) == tuple :
3920+ (intf_name , ip ) = k
3921+ if intf_name == interface_name and ip != 0 :
3922+ ctx .fail ("%s is configured with IP %s, remove the IP configuration and reconfigure after enabling unique IP configuration." % (str (intf_name ), str (ip )))
3923+ db .set_entry ('MCLAG_UNIQUE_IP' , (interface_name ), {'unique_ip' :"enable" } )
3924+
3925+ @mclag_unique_ip .command ('del' )
3926+ @click .argument ('interface_names' , metavar = '<interface_names>' , required = True )
3927+ @click .pass_context
3928+ def del_mclag_unique_ip (ctx , interface_names ):
3929+ """Delete unique ip from MCLAG vlan interface"""
3930+ db = ctx .obj ['db' ]
3931+ #split comma seperated interface names
3932+ interface_list = interface_names .split ("," )
3933+ for interface_name in interface_list :
3934+ if not interface_name .startswith ("Vlan" ):
3935+ ctx .fail ("{} is invalid!, name should have prefix '{}' and suffix '{}'" .format (interface_name , "Vlan" , "vlan id" ))
3936+ #VRF should be configured after removing unique IP configuration
3937+ intf_vrf = get_intf_vrf_bind_unique_ip (db , interface_name , "VLAN_INTERFACE" )
3938+ if intf_vrf :
3939+ ctx .fail ("%s is configured with Non default VRF, remove the VRF configuration and reconfigure after disabling unique IP configuration." % (str (interface_name )))
3940+ #IP should be configured after removing unique IP configuration
3941+ for k ,v in db .get_table ('VLAN_INTERFACE' ).iteritems ():
3942+ if type (k ) == tuple :
3943+ (intf_name , ip ) = k
3944+ if intf_name == interface_name and ip != 0 :
3945+ ctx .fail ("%s is configured with IP %s, remove the IP configuration and reconfigure after disabling unique IP configuration." % (str (intf_name ), str (ip )))
3946+ db .set_entry ('MCLAG_UNIQUE_IP' , (interface_name ), None )
3947+
3948+ #######
36713949
36723950if __name__ == '__main__' :
36733951 config ()
0 commit comments