Skip to content

Commit

Permalink
Minigraph ECMP parsing support (cleaner format) (#4985)
Browse files Browse the repository at this point in the history
 Why I did it
To support FG_ECMP  scenarios
- How I did it
Modified minigraph parser to parse ECMP fields in the case they are present in minigraph
- How to verify it
Loaded ensuing config_db file on a DUT to verify the fields are parsed and configure device correctly
  • Loading branch information
kktheballer authored Dec 30, 2020
1 parent a1fe203 commit ba92a08
Show file tree
Hide file tree
Showing 4 changed files with 1,839 additions and 9 deletions.
162 changes: 153 additions & 9 deletions src/sonic-config-engine/minigraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from lxml import etree as ET
from lxml.etree import QName


from portconfig import get_port_config
from sonic_py_common.multi_asic import get_asic_id_from_name
from sonic_py_common.interface import backplane_prefix
Expand Down Expand Up @@ -105,7 +106,73 @@ def parse_device(device):

return (lo_prefix, lo_prefix_v6, mgmt_prefix, name, hwsku, d_type, deployment_id)

def parse_png(png, hname):
def calculate_lcm_for_ecmp (nhdevices_bank_map, nhip_bank_map):
banks_enumerated = {}
lcm_array = []
for value in nhdevices_bank_map.values():
for key in nhip_bank_map.keys():
if nhip_bank_map[key] == value:
if value not in banks_enumerated:
banks_enumerated[value] = 1
else:
banks_enumerated[value] = banks_enumerated[value] + 1
for bank_enumeration in banks_enumerated.values():
lcm_list = range(1, bank_enumeration+1)
lcm_comp = lcm_list[0]
for i in lcm_list[1:]:
lcm_comp = lcm_comp * i / calculate_gcd(lcm_comp, i)
lcm_array.append(lcm_comp)

LCM = sum(lcm_array)
return int(LCM)

def calculate_gcd(x, y):
while y != 0:
(x, y) = (y, x % y)
return int(x)

def formulate_fine_grained_ecmp(version, dpg_ecmp_content, port_device_map, port_alias_map):
family = ""
tag = ""
neigh_key = []
if version == "ipv4":
family = "IPV4"
tag = "fgnhg_v4"
elif version == "ipv6":
family = "IPV6"
tag = "fgnhg_v6"

port_nhip_map = dpg_ecmp_content['port_nhip_map']
nhgaddr = dpg_ecmp_content['nhgaddr']
nhg_int = dpg_ecmp_content['nhg_int']

nhip_device_map = {port_nhip_map[x]: port_device_map[x] for x in port_device_map
if x in port_nhip_map}
nhip_devices = sorted(list(set(nhip_device_map.values())))
nhdevices_ip_bank_map = {device: bank for bank, device in enumerate(nhip_devices)}
nhip_bank_map = {ip: nhdevices_ip_bank_map[device] for ip, device in nhip_device_map.items()}
LCM = calculate_lcm_for_ecmp(nhdevices_ip_bank_map, nhip_bank_map)

FG_NHG_MEMBER = {ip: {"FG_NHG": tag, "bank": bank} for ip, bank in nhip_bank_map.items()}
nhip_port_map = dict(zip(port_nhip_map.values(), port_nhip_map.keys()))



for nhip, memberinfo in FG_NHG_MEMBER.items():
if nhip in nhip_port_map:
memberinfo["link"] = port_alias_map[nhip_port_map[nhip]]
FG_NHG_MEMBER[nhip] = memberinfo

FG_NHG_PREFIX = {nhgaddr: {"FG_NHG": tag}}
FG_NHG = {tag: {"bucket_size": LCM}}
for ip in nhip_bank_map:
neigh_key.append(str(nhg_int + "|" + ip))
NEIGH = {neigh_key: {"family": family} for neigh_key in neigh_key}

fine_grained_content = {"FG_NHG_MEMBER": FG_NHG_MEMBER, "FG_NHG": FG_NHG, "FG_NHG_PREFIX": FG_NHG_PREFIX, "NEIGH": NEIGH}
return fine_grained_content

def parse_png(png, hname, dpg_ecmp_content = None):
neighbors = {}
devices = {}
console_dev = ''
Expand All @@ -116,6 +183,13 @@ def parse_png(png, hname):
console_ports = {}
mux_cable_ports = {}
is_storage_device = False
port_device_map = {}
png_ecmp_content = {}
FG_NHG_MEMBER = {}
FG_NHG_PREFIX = {}
FG_NHG = {}
NEIGH = {}

for child in png:
if child.tag == str(QName(ns, "DeviceInterfaceLinks")):
for link in child.findall(str(QName(ns, "DeviceLinkBase"))):
Expand All @@ -141,6 +215,11 @@ def parse_png(png, hname):
}
continue

if linktype == "DeviceInterfaceLink":
endport = link.find(str(QName(ns, "EndPort"))).text
startdevice = link.find(str(QName(ns, "StartDevice"))).text
port_device_map[endport] = startdevice

if linktype != "DeviceInterfaceLink" and linktype != "UnderlayInterfaceLink":
continue

Expand Down Expand Up @@ -196,6 +275,7 @@ def parse_png(png, hname):
elif node.tag == str(QName(ns, "EndDevice")):
mgmt_dev = node.text


if child.tag == str(QName(ns, "DeviceInterfaceLinks")):
for link in child.findall(str(QName(ns, 'DeviceLinkBase'))):
if link.find(str(QName(ns, "ElementType"))).text == "LogicalLink":
Expand All @@ -205,7 +285,19 @@ def parse_png(png, hname):

mux_cable_ports[intf_name] = "true"

return (neighbors, devices, console_dev, console_port, mgmt_dev, mgmt_port, port_speeds, console_ports, mux_cable_ports, is_storage_device)
if (len(dpg_ecmp_content)):
for version, content in dpg_ecmp_content.items(): # version is ipv4 or ipv6
fine_grained_content = formulate_fine_grained_ecmp(version, content, port_device_map, port_alias_map) # port_alias_map
FG_NHG_MEMBER.update(fine_grained_content['FG_NHG_MEMBER'])
FG_NHG_PREFIX.update(fine_grained_content['FG_NHG_PREFIX'])
FG_NHG.update(fine_grained_content['FG_NHG'])
NEIGH.update(fine_grained_content['NEIGH'])

png_ecmp_content = {"FG_NHG_PREFIX": FG_NHG_PREFIX, "FG_NHG_MEMBER": FG_NHG_MEMBER, "FG_NHG": FG_NHG,
"NEIGH": NEIGH}

return (neighbors, devices, console_dev, console_port, mgmt_dev, mgmt_port, port_speeds, console_ports, mux_cable_ports, is_storage_device, png_ecmp_content)


def parse_asic_external_link(link, asic_name, hostname):
neighbors = {}
Expand Down Expand Up @@ -295,6 +387,8 @@ def parse_asic_png(png, asic_name, hostname):
if lo_prefix_v6:
device_data['lo_addr_v6']= lo_prefix_v6
devices[name] = device_data


return (neighbors, devices, port_speeds)

def parse_loopback_intf(child):
Expand Down Expand Up @@ -339,12 +433,13 @@ def parse_dpg(dpg, hname):

ipintfs = child.find(str(QName(ns, "IPInterfaces")))
intfs = {}
ip_intfs_map = {}
for ipintf in ipintfs.findall(str(QName(ns, "IPInterface"))):
intfalias = ipintf.find(str(QName(ns, "AttachTo"))).text
intfname = port_alias_map.get(intfalias, intfalias)
ipprefix = ipintf.find(str(QName(ns, "Prefix"))).text
intfs[(intfname, ipprefix)] = {}

ip_intfs_map[ipprefix] = intfalias
lo_intfs = parse_loopback_intf(child)

mvrfConfigs = child.find(str(QName(ns, "MgmtVrfConfigs")))
Expand Down Expand Up @@ -381,7 +476,46 @@ def parse_dpg(dpg, hname):
pcs[pcintfname] = {'members': pcmbr_list, 'fallback': pcintf.find(str(QName(ns, "Fallback"))).text, 'min_links': str(int(math.ceil(len() * 0.75)))}
else:
pcs[pcintfname] = {'members': pcmbr_list, 'min_links': str(int(math.ceil(len(pcmbr_list) * 0.75)))}

port_nhipv4_map = {}
port_nhipv6_map = {}
nhgaddr = ["", ""]
nhg_int = ""
nhportlist = []
dpg_ecmp_content = {}
ipnhs = child.find(str(QName(ns, "IPNextHops")))
if ipnhs is not None:
for ipnh in ipnhs.findall(str(QName(ns, "IPNextHop"))):
if ipnh.find(str(QName(ns, "Type"))).text == 'FineGrainedECMPGroupMember':
ipnhfmbr = ipnh.find(str(QName(ns, "AttachTo"))).text
ipnhaddr = ipnh.find(str(QName(ns, "Address"))).text
nhportlist.append(ipnhfmbr)
if "." in ipnhaddr:
port_nhipv4_map[ipnhfmbr] = ipnhaddr
elif ":" in ipnhaddr:
port_nhipv6_map[ipnhfmbr] = ipnhaddr

if port_nhipv4_map is not None and port_nhipv6_map is not None:
subnet_check_ip = list(port_nhipv4_map.values())[0]
for subnet_range in ip_intfs_map:
if ("." in subnet_range):
a = ipaddress.ip_address(UNICODE_TYPE(subnet_check_ip))
n = list(ipaddress.ip_network(UNICODE_TYPE(subnet_range), False).hosts())
if a in n:
nhg_int = ip_intfs_map[subnet_range]
dwnstrms = child.find(str(QName(ns, "DownstreamSummarySet")))
for dwnstrm in dwnstrms.findall(str(QName(ns, "DownstreamSummary"))):
dwnstrmentry = str(ET.tostring(dwnstrm))
if ("FineGrainedECMPGroupDestination" in dwnstrmentry):
subnet_ip = dwnstrm.find(str(QName(ns1, "Subnet"))).text
truncsubnet_ip = subnet_ip.split("/")[0]
if "." in (truncsubnet_ip):
nhgaddr[0] = subnet_ip
elif ":" in (truncsubnet_ip):
nhgaddr[1] = subnet_ip
ipv4_content = {"port_nhip_map": port_nhipv4_map, "nhgaddr": nhgaddr[0], "nhg_int": nhg_int}
ipv6_content = {"port_nhip_map": port_nhipv6_map, "nhgaddr": nhgaddr[1], "nhg_int": nhg_int}
dpg_ecmp_content['ipv4'] = ipv4_content
dpg_ecmp_content['ipv6'] = ipv6_content
vlanintfs = child.find(str(QName(ns, "VlanInterfaces")))
vlan_intfs = []
vlans = {}
Expand Down Expand Up @@ -503,6 +637,7 @@ def parse_dpg(dpg, hname):
except:
print("Warning: Ignoring Control Plane ACL %s without type" % aclname, file=sys.stderr)


mg_tunnels = child.find(str(QName(ns, "TunnelInterfaces")))
if mg_tunnels is not None:
table_key_to_mg_key_map = {"encap_ecn_mode": "EcnEncapsulationMode",
Expand All @@ -521,7 +656,7 @@ def parse_dpg(dpg, hname):
if mg_key in mg_tunnel.attrib:
tunnelintfs[tunnel_type][tunnel_name][table_key] = mg_tunnel.attrib[mg_key]

return intfs, lo_intfs, mvrf, mgmt_intf, vlans, vlan_members, pcs, pc_members, acls, vni, tunnelintfs
return intfs, lo_intfs, mvrf, mgmt_intf, vlans, vlan_members, pcs, pc_members, acls, vni, tunnelintfs, dpg_ecmp_content
return None, None, None, None, None, None, None, None, None, None

def parse_host_loopback(dpg, hname):
Expand Down Expand Up @@ -927,6 +1062,8 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw
bgp_monitors = []
bgp_asn = None
intfs = None
dpg_ecmp_content = {}
png_ecmp_content = {}
vlan_intfs = None
pc_intfs = None
tunnel_intfs = None
Expand Down Expand Up @@ -988,13 +1125,13 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw
for child in root:
if asic_name is None:
if child.tag == str(QName(ns, "DpgDec")):
(intfs, lo_intfs, mvrf, mgmt_intf, vlans, vlan_members, pcs, pc_members, acls, vni, tunnel_intfs) = parse_dpg(child, hostname)
(intfs, lo_intfs, mvrf, mgmt_intf, vlans, vlan_members, pcs, pc_members, acls, vni, tunnel_intfs, dpg_ecmp_content) = parse_dpg(child, hostname)
elif child.tag == str(QName(ns, "CpgDec")):
(bgp_sessions, bgp_internal_sessions, bgp_asn, bgp_peers_with_range, bgp_monitors) = parse_cpg(child, hostname)
elif child.tag == str(QName(ns, "PngDec")):
(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)
(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)
elif child.tag == str(QName(ns, "UngDec")):
(u_neighbors, u_devices, _, _, _, _, _, _) = parse_png(child, hostname)
(u_neighbors, u_devices, _, _, _, _, _, _) = parse_png(child, hostname, None)
elif child.tag == str(QName(ns, "MetadataDeclaration")):
(syslog_servers, dhcp_servers, ntp_servers, tacacs_servers, mgmt_routes, erspan_dst, deployment_id, region, cloudtype, resource_type) = parse_meta(child, hostname)
elif child.tag == str(QName(ns, "LinkMetadataDeclaration")):
Expand All @@ -1003,7 +1140,7 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw
(port_speeds_default, port_descriptions) = parse_deviceinfo(child, hwsku)
else:
if child.tag == str(QName(ns, "DpgDec")):
(intfs, lo_intfs, mvrf, mgmt_intf, vlans, vlan_members, pcs, pc_members, acls, vni, tunnel_intfs) = parse_dpg(child, asic_name)
(intfs, lo_intfs, mvrf, mgmt_intf, vlans, vlan_members, pcs, pc_members, acls, vni, tunnel_intfs, dpg_ecmp_content) = parse_dpg(child, asic_name)
host_lo_intfs = parse_host_loopback(child, hostname)
elif child.tag == str(QName(ns, "CpgDec")):
(bgp_sessions, bgp_internal_sessions, bgp_asn, bgp_peers_with_range, bgp_monitors) = parse_cpg(child, asic_name, local_devices)
Expand Down Expand Up @@ -1305,6 +1442,13 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw
'client_crt_cname': 'client.restapi.sonic'
}
}

if len(png_ecmp_content):
results['FG_NHG_MEMBER'] = png_ecmp_content['FG_NHG_MEMBER']
results['FG_NHG_PREFIX'] = png_ecmp_content['FG_NHG_PREFIX']
results['FG_NHG'] = png_ecmp_content['FG_NHG']
results['NEIGH'] = png_ecmp_content['NEIGH']

# Do not configure the minigraph's mirror session, which is currently unused
# mirror_sessions = {}
# if erspan_dst:
Expand Down
Loading

0 comments on commit ba92a08

Please sign in to comment.