1010from lxml import etree as ET
1111from lxml .etree import QName
1212
13+
1314from portconfig import get_port_config
1415from sonic_py_common .multi_asic import get_asic_id_from_name
1516from sonic_py_common .interface import backplane_prefix
@@ -105,7 +106,73 @@ def parse_device(device):
105106
106107 return (lo_prefix , lo_prefix_v6 , mgmt_prefix , name , hwsku , d_type , deployment_id )
107108
108- def parse_png (png , hname ):
109+ def calculate_lcm_for_ecmp (nhdevices_bank_map , nhip_bank_map ):
110+ banks_enumerated = {}
111+ lcm_array = []
112+ for value in nhdevices_bank_map .values ():
113+ for key in nhip_bank_map .keys ():
114+ if nhip_bank_map [key ] == value :
115+ if value not in banks_enumerated :
116+ banks_enumerated [value ] = 1
117+ else :
118+ banks_enumerated [value ] = banks_enumerated [value ] + 1
119+ for bank_enumeration in banks_enumerated .values ():
120+ lcm_list = range (1 , bank_enumeration + 1 )
121+ lcm_comp = lcm_list [0 ]
122+ for i in lcm_list [1 :]:
123+ lcm_comp = lcm_comp * i / calculate_gcd (lcm_comp , i )
124+ lcm_array .append (lcm_comp )
125+
126+ LCM = sum (lcm_array )
127+ return int (LCM )
128+
129+ def calculate_gcd (x , y ):
130+ while y != 0 :
131+ (x , y ) = (y , x % y )
132+ return int (x )
133+
134+ def formulate_fine_grained_ecmp (version , dpg_ecmp_content , port_device_map , port_alias_map ):
135+ family = ""
136+ tag = ""
137+ neigh_key = []
138+ if version == "ipv4" :
139+ family = "IPV4"
140+ tag = "fgnhg_v4"
141+ elif version == "ipv6" :
142+ family = "IPV6"
143+ tag = "fgnhg_v6"
144+
145+ port_nhip_map = dpg_ecmp_content ['port_nhip_map' ]
146+ nhgaddr = dpg_ecmp_content ['nhgaddr' ]
147+ nhg_int = dpg_ecmp_content ['nhg_int' ]
148+
149+ nhip_device_map = {port_nhip_map [x ]: port_device_map [x ] for x in port_device_map
150+ if x in port_nhip_map }
151+ nhip_devices = sorted (list (set (nhip_device_map .values ())))
152+ nhdevices_ip_bank_map = {device : bank for bank , device in enumerate (nhip_devices )}
153+ nhip_bank_map = {ip : nhdevices_ip_bank_map [device ] for ip , device in nhip_device_map .items ()}
154+ LCM = calculate_lcm_for_ecmp (nhdevices_ip_bank_map , nhip_bank_map )
155+
156+ FG_NHG_MEMBER = {ip : {"FG_NHG" : tag , "bank" : bank } for ip , bank in nhip_bank_map .items ()}
157+ nhip_port_map = dict (zip (port_nhip_map .values (), port_nhip_map .keys ()))
158+
159+
160+
161+ for nhip , memberinfo in FG_NHG_MEMBER .items ():
162+ if nhip in nhip_port_map :
163+ memberinfo ["link" ] = port_alias_map [nhip_port_map [nhip ]]
164+ FG_NHG_MEMBER [nhip ] = memberinfo
165+
166+ FG_NHG_PREFIX = {nhgaddr : {"FG_NHG" : tag }}
167+ FG_NHG = {tag : {"bucket_size" : LCM }}
168+ for ip in nhip_bank_map :
169+ neigh_key .append (str (nhg_int + "|" + ip ))
170+ NEIGH = {neigh_key : {"family" : family } for neigh_key in neigh_key }
171+
172+ fine_grained_content = {"FG_NHG_MEMBER" : FG_NHG_MEMBER , "FG_NHG" : FG_NHG , "FG_NHG_PREFIX" : FG_NHG_PREFIX , "NEIGH" : NEIGH }
173+ return fine_grained_content
174+
175+ def parse_png (png , hname , dpg_ecmp_content = None ):
109176 neighbors = {}
110177 devices = {}
111178 console_dev = ''
@@ -116,6 +183,13 @@ def parse_png(png, hname):
116183 console_ports = {}
117184 mux_cable_ports = {}
118185 is_storage_device = False
186+ port_device_map = {}
187+ png_ecmp_content = {}
188+ FG_NHG_MEMBER = {}
189+ FG_NHG_PREFIX = {}
190+ FG_NHG = {}
191+ NEIGH = {}
192+
119193 for child in png :
120194 if child .tag == str (QName (ns , "DeviceInterfaceLinks" )):
121195 for link in child .findall (str (QName (ns , "DeviceLinkBase" ))):
@@ -141,6 +215,11 @@ def parse_png(png, hname):
141215 }
142216 continue
143217
218+ if linktype == "DeviceInterfaceLink" :
219+ endport = link .find (str (QName (ns , "EndPort" ))).text
220+ startdevice = link .find (str (QName (ns , "StartDevice" ))).text
221+ port_device_map [endport ] = startdevice
222+
144223 if linktype != "DeviceInterfaceLink" and linktype != "UnderlayInterfaceLink" :
145224 continue
146225
@@ -196,6 +275,7 @@ def parse_png(png, hname):
196275 elif node .tag == str (QName (ns , "EndDevice" )):
197276 mgmt_dev = node .text
198277
278+
199279 if child .tag == str (QName (ns , "DeviceInterfaceLinks" )):
200280 for link in child .findall (str (QName (ns , 'DeviceLinkBase' ))):
201281 if link .find (str (QName (ns , "ElementType" ))).text == "LogicalLink" :
@@ -205,7 +285,19 @@ def parse_png(png, hname):
205285
206286 mux_cable_ports [intf_name ] = "true"
207287
208- return (neighbors , devices , console_dev , console_port , mgmt_dev , mgmt_port , port_speeds , console_ports , mux_cable_ports , is_storage_device )
288+ if (len (dpg_ecmp_content )):
289+ for version , content in dpg_ecmp_content .items (): # version is ipv4 or ipv6
290+ fine_grained_content = formulate_fine_grained_ecmp (version , content , port_device_map , port_alias_map ) # port_alias_map
291+ FG_NHG_MEMBER .update (fine_grained_content ['FG_NHG_MEMBER' ])
292+ FG_NHG_PREFIX .update (fine_grained_content ['FG_NHG_PREFIX' ])
293+ FG_NHG .update (fine_grained_content ['FG_NHG' ])
294+ NEIGH .update (fine_grained_content ['NEIGH' ])
295+
296+ png_ecmp_content = {"FG_NHG_PREFIX" : FG_NHG_PREFIX , "FG_NHG_MEMBER" : FG_NHG_MEMBER , "FG_NHG" : FG_NHG ,
297+ "NEIGH" : NEIGH }
298+
299+ return (neighbors , devices , console_dev , console_port , mgmt_dev , mgmt_port , port_speeds , console_ports , mux_cable_ports , is_storage_device , png_ecmp_content )
300+
209301
210302def parse_asic_external_link (link , asic_name , hostname ):
211303 neighbors = {}
@@ -295,6 +387,8 @@ def parse_asic_png(png, asic_name, hostname):
295387 if lo_prefix_v6 :
296388 device_data ['lo_addr_v6' ]= lo_prefix_v6
297389 devices [name ] = device_data
390+
391+
298392 return (neighbors , devices , port_speeds )
299393
300394def parse_loopback_intf (child ):
@@ -339,12 +433,13 @@ def parse_dpg(dpg, hname):
339433
340434 ipintfs = child .find (str (QName (ns , "IPInterfaces" )))
341435 intfs = {}
436+ ip_intfs_map = {}
342437 for ipintf in ipintfs .findall (str (QName (ns , "IPInterface" ))):
343438 intfalias = ipintf .find (str (QName (ns , "AttachTo" ))).text
344439 intfname = port_alias_map .get (intfalias , intfalias )
345440 ipprefix = ipintf .find (str (QName (ns , "Prefix" ))).text
346441 intfs [(intfname , ipprefix )] = {}
347-
442+ ip_intfs_map [ ipprefix ] = intfalias
348443 lo_intfs = parse_loopback_intf (child )
349444
350445 mvrfConfigs = child .find (str (QName (ns , "MgmtVrfConfigs" )))
@@ -381,7 +476,46 @@ def parse_dpg(dpg, hname):
381476 pcs [pcintfname ] = {'members' : pcmbr_list , 'fallback' : pcintf .find (str (QName (ns , "Fallback" ))).text , 'min_links' : str (int (math .ceil (len () * 0.75 )))}
382477 else :
383478 pcs [pcintfname ] = {'members' : pcmbr_list , 'min_links' : str (int (math .ceil (len (pcmbr_list ) * 0.75 )))}
384-
479+ port_nhipv4_map = {}
480+ port_nhipv6_map = {}
481+ nhgaddr = ["" , "" ]
482+ nhg_int = ""
483+ nhportlist = []
484+ dpg_ecmp_content = {}
485+ ipnhs = child .find (str (QName (ns , "IPNextHops" )))
486+ if ipnhs is not None :
487+ for ipnh in ipnhs .findall (str (QName (ns , "IPNextHop" ))):
488+ if ipnh .find (str (QName (ns , "Type" ))).text == 'FineGrainedECMPGroupMember' :
489+ ipnhfmbr = ipnh .find (str (QName (ns , "AttachTo" ))).text
490+ ipnhaddr = ipnh .find (str (QName (ns , "Address" ))).text
491+ nhportlist .append (ipnhfmbr )
492+ if "." in ipnhaddr :
493+ port_nhipv4_map [ipnhfmbr ] = ipnhaddr
494+ elif ":" in ipnhaddr :
495+ port_nhipv6_map [ipnhfmbr ] = ipnhaddr
496+
497+ if port_nhipv4_map is not None and port_nhipv6_map is not None :
498+ subnet_check_ip = list (port_nhipv4_map .values ())[0 ]
499+ for subnet_range in ip_intfs_map :
500+ if ("." in subnet_range ):
501+ a = ipaddress .ip_address (UNICODE_TYPE (subnet_check_ip ))
502+ n = list (ipaddress .ip_network (UNICODE_TYPE (subnet_range ), False ).hosts ())
503+ if a in n :
504+ nhg_int = ip_intfs_map [subnet_range ]
505+ dwnstrms = child .find (str (QName (ns , "DownstreamSummarySet" )))
506+ for dwnstrm in dwnstrms .findall (str (QName (ns , "DownstreamSummary" ))):
507+ dwnstrmentry = str (ET .tostring (dwnstrm ))
508+ if ("FineGrainedECMPGroupDestination" in dwnstrmentry ):
509+ subnet_ip = dwnstrm .find (str (QName (ns1 , "Subnet" ))).text
510+ truncsubnet_ip = subnet_ip .split ("/" )[0 ]
511+ if "." in (truncsubnet_ip ):
512+ nhgaddr [0 ] = subnet_ip
513+ elif ":" in (truncsubnet_ip ):
514+ nhgaddr [1 ] = subnet_ip
515+ ipv4_content = {"port_nhip_map" : port_nhipv4_map , "nhgaddr" : nhgaddr [0 ], "nhg_int" : nhg_int }
516+ ipv6_content = {"port_nhip_map" : port_nhipv6_map , "nhgaddr" : nhgaddr [1 ], "nhg_int" : nhg_int }
517+ dpg_ecmp_content ['ipv4' ] = ipv4_content
518+ dpg_ecmp_content ['ipv6' ] = ipv6_content
385519 vlanintfs = child .find (str (QName (ns , "VlanInterfaces" )))
386520 vlan_intfs = []
387521 vlans = {}
@@ -503,6 +637,7 @@ def parse_dpg(dpg, hname):
503637 except :
504638 print ("Warning: Ignoring Control Plane ACL %s without type" % aclname , file = sys .stderr )
505639
640+
506641 mg_tunnels = child .find (str (QName (ns , "TunnelInterfaces" )))
507642 if mg_tunnels is not None :
508643 table_key_to_mg_key_map = {"encap_ecn_mode" : "EcnEncapsulationMode" ,
@@ -521,7 +656,7 @@ def parse_dpg(dpg, hname):
521656 if mg_key in mg_tunnel .attrib :
522657 tunnelintfs [tunnel_type ][tunnel_name ][table_key ] = mg_tunnel .attrib [mg_key ]
523658
524- return intfs , lo_intfs , mvrf , mgmt_intf , vlans , vlan_members , pcs , pc_members , acls , vni , tunnelintfs
659+ return intfs , lo_intfs , mvrf , mgmt_intf , vlans , vlan_members , pcs , pc_members , acls , vni , tunnelintfs , dpg_ecmp_content
525660 return None , None , None , None , None , None , None , None , None , None
526661
527662def parse_host_loopback (dpg , hname ):
@@ -927,6 +1062,8 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw
9271062 bgp_monitors = []
9281063 bgp_asn = None
9291064 intfs = None
1065+ dpg_ecmp_content = {}
1066+ png_ecmp_content = {}
9301067 vlan_intfs = None
9311068 pc_intfs = None
9321069 tunnel_intfs = None
@@ -988,13 +1125,13 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw
9881125 for child in root :
9891126 if asic_name is None :
9901127 if child .tag == str (QName (ns , "DpgDec" )):
991- (intfs , lo_intfs , mvrf , mgmt_intf , vlans , vlan_members , pcs , pc_members , acls , vni , tunnel_intfs ) = parse_dpg (child , hostname )
1128+ (intfs , lo_intfs , mvrf , mgmt_intf , vlans , vlan_members , pcs , pc_members , acls , vni , tunnel_intfs , dpg_ecmp_content ) = parse_dpg (child , hostname )
9921129 elif child .tag == str (QName (ns , "CpgDec" )):
9931130 (bgp_sessions , bgp_internal_sessions , bgp_asn , bgp_peers_with_range , bgp_monitors ) = parse_cpg (child , hostname )
9941131 elif child .tag == str (QName (ns , "PngDec" )):
995- (neighbors , devices , console_dev , console_port , mgmt_dev , mgmt_port , port_speed_png , console_ports , mux_cable_ports , is_storage_device ) = parse_png (child , hostname )
1132+ (neighbors , devices , console_dev , console_port , mgmt_dev , mgmt_port , port_speed_png , console_ports , mux_cable_ports , is_storage_device , png_ecmp_content ) = parse_png (child , hostname , dpg_ecmp_content )
9961133 elif child .tag == str (QName (ns , "UngDec" )):
997- (u_neighbors , u_devices , _ , _ , _ , _ , _ , _ ) = parse_png (child , hostname )
1134+ (u_neighbors , u_devices , _ , _ , _ , _ , _ , _ ) = parse_png (child , hostname , None )
9981135 elif child .tag == str (QName (ns , "MetadataDeclaration" )):
9991136 (syslog_servers , dhcp_servers , ntp_servers , tacacs_servers , mgmt_routes , erspan_dst , deployment_id , region , cloudtype , resource_type ) = parse_meta (child , hostname )
10001137 elif child .tag == str (QName (ns , "LinkMetadataDeclaration" )):
@@ -1003,7 +1140,7 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw
10031140 (port_speeds_default , port_descriptions ) = parse_deviceinfo (child , hwsku )
10041141 else :
10051142 if child .tag == str (QName (ns , "DpgDec" )):
1006- (intfs , lo_intfs , mvrf , mgmt_intf , vlans , vlan_members , pcs , pc_members , acls , vni , tunnel_intfs ) = parse_dpg (child , asic_name )
1143+ (intfs , lo_intfs , mvrf , mgmt_intf , vlans , vlan_members , pcs , pc_members , acls , vni , tunnel_intfs , dpg_ecmp_content ) = parse_dpg (child , asic_name )
10071144 host_lo_intfs = parse_host_loopback (child , hostname )
10081145 elif child .tag == str (QName (ns , "CpgDec" )):
10091146 (bgp_sessions , bgp_internal_sessions , bgp_asn , bgp_peers_with_range , bgp_monitors ) = parse_cpg (child , asic_name , local_devices )
@@ -1305,6 +1442,13 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw
13051442 'client_crt_cname' : 'client.restapi.sonic'
13061443 }
13071444 }
1445+
1446+ if len (png_ecmp_content ):
1447+ results ['FG_NHG_MEMBER' ] = png_ecmp_content ['FG_NHG_MEMBER' ]
1448+ results ['FG_NHG_PREFIX' ] = png_ecmp_content ['FG_NHG_PREFIX' ]
1449+ results ['FG_NHG' ] = png_ecmp_content ['FG_NHG' ]
1450+ results ['NEIGH' ] = png_ecmp_content ['NEIGH' ]
1451+
13081452 # Do not configure the minigraph's mirror session, which is currently unused
13091453 # mirror_sessions = {}
13101454 # if erspan_dst:
0 commit comments