DEPENDS ON: sonic-net/sonic-swss#2997 sonic-net/sonic-utilities#3093 What I did Revert the feature. Why I did it Revert bgp suppress FIB functionality due to found FRR memory consumption issues and bugs. How I verified it Basic sanity check on t1-lag, regression in progress.
2256 lines
102 KiB
Python
2256 lines
102 KiB
Python
from __future__ import print_function
|
|
|
|
import ipaddress
|
|
import math
|
|
import os
|
|
import sys
|
|
import json
|
|
import subprocess
|
|
from collections import defaultdict
|
|
|
|
from lxml import etree as ET
|
|
from lxml.etree import QName
|
|
|
|
from natsort import natsorted, ns as natsortns
|
|
|
|
from portconfig import get_port_config, get_fabric_port_config, get_fabric_monitor_config
|
|
from sonic_py_common.interface import backplane_prefix
|
|
|
|
# TODO: Remove this once we no longer support Python 2
|
|
if sys.version_info.major == 3:
|
|
UNICODE_TYPE = str
|
|
else:
|
|
UNICODE_TYPE = unicode
|
|
|
|
"""minigraph.py
|
|
version_added: "1.9"
|
|
author: Guohan Lu (gulv@microsoft.com)
|
|
short_description: Parse minigraph xml file and device description xml file
|
|
"""
|
|
|
|
ns = "Microsoft.Search.Autopilot.Evolution"
|
|
ns1 = "http://schemas.datacontract.org/2004/07/Microsoft.Search.Autopilot.Evolution"
|
|
ns2 = "Microsoft.Search.Autopilot.NetMux"
|
|
ns3 = "http://www.w3.org/2001/XMLSchema-instance"
|
|
|
|
# Device types
|
|
spine_chassis_frontend_role = 'SpineChassisFrontendRouter'
|
|
chassis_backend_role = 'ChassisBackendRouter'
|
|
|
|
backend_device_types = ['BackEndToRRouter', 'BackEndLeafRouter']
|
|
console_device_types = ['MgmtTsToR']
|
|
dhcp_server_enabled_device_types = ['BmcMgmtToRRouter']
|
|
mgmt_device_types = ['BmcMgmtToRRouter', 'MgmtToRRouter', 'MgmtTsToR']
|
|
leafrouter_device_types = ['LeafRouter']
|
|
|
|
# Counters disabled on management devices
|
|
mgmt_disabled_counters = ["BUFFER_POOL_WATERMARK", "PFCWD", "PG_DROP", "PG_WATERMARK", "PORT_BUFFER_DROP", "QUEUE", "QUEUE_WATERMARK"]
|
|
|
|
VLAN_SUB_INTERFACE_SEPARATOR = '.'
|
|
VLAN_SUB_INTERFACE_VLAN_ID = '10'
|
|
|
|
FRONTEND_ASIC_SUB_ROLE = 'FrontEnd'
|
|
BACKEND_ASIC_SUB_ROLE = 'BackEnd'
|
|
|
|
dualtor_cable_types = ["active-active", "active-standby"]
|
|
|
|
# Default Virtual Network Index (VNI)
|
|
vni_default = 8000
|
|
|
|
# Defination of custom acl table types
|
|
acl_table_type_defination = {
|
|
'BMCDATA': {
|
|
"ACTIONS": ["PACKET_ACTION", "COUNTER"],
|
|
"BIND_POINTS": ["PORT"],
|
|
"MATCHES": ["SRC_IP", "DST_IP", "ETHER_TYPE", "IP_TYPE", "IP_PROTOCOL", "IN_PORTS", "L4_SRC_PORT", "L4_DST_PORT", "L4_SRC_PORT_RANGE", "L4_DST_PORT_RANGE"]
|
|
},
|
|
'BMCDATAV6': {
|
|
"ACTIONS": ["PACKET_ACTION", "COUNTER"],
|
|
"BIND_POINTS": ["PORT"],
|
|
"MATCHES": ["SRC_IPV6", "DST_IPV6", "ETHER_TYPE", "IP_TYPE", "IP_PROTOCOL", "IN_PORTS", "L4_SRC_PORT", "L4_DST_PORT", "L4_SRC_PORT_RANGE", "L4_DST_PORT_RANGE", "ICMPV6_TYPE", "ICMPV6_CODE", "TCP_FLAGS"]
|
|
}
|
|
}
|
|
|
|
###############################################################################
|
|
#
|
|
# Minigraph parsing functions
|
|
#
|
|
###############################################################################
|
|
|
|
class minigraph_encoder(json.JSONEncoder):
|
|
def default(self, obj):
|
|
if isinstance(obj, (
|
|
ipaddress.IPv4Network, ipaddress.IPv6Network,
|
|
ipaddress.IPv4Address, ipaddress.IPv6Address
|
|
)):
|
|
return str(obj)
|
|
return json.JSONEncoder.default(self, obj)
|
|
|
|
def exec_cmd(cmd):
|
|
p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE)
|
|
outs, errs = p.communicate()
|
|
|
|
def get_peer_switch_info(link_metadata, devices):
|
|
peer_switch_table = {}
|
|
peer_switch_ip = None
|
|
mux_tunnel_name = None
|
|
for port, data in link_metadata.items():
|
|
if "PeerSwitch" in data:
|
|
peer_hostname = data["PeerSwitch"]
|
|
peer_lo_addr_str = devices[peer_hostname]["lo_addr"]
|
|
peer_lo_addr = ipaddress.ip_network(UNICODE_TYPE(peer_lo_addr_str)) if peer_lo_addr_str else None
|
|
|
|
peer_switch_table[peer_hostname] = {
|
|
'address_ipv4': str(peer_lo_addr.network_address) if peer_lo_addr else peer_lo_addr_str
|
|
}
|
|
mux_tunnel_name = port
|
|
peer_switch_ip = peer_switch_table[peer_hostname]['address_ipv4']
|
|
|
|
return peer_switch_table, mux_tunnel_name, peer_switch_ip
|
|
|
|
|
|
def parse_device(device):
|
|
lo_prefix = None
|
|
lo_prefix_v6 = None
|
|
mgmt_prefix = None
|
|
mgmt_prefix_v6 = None
|
|
d_type = None # don't shadow type()
|
|
hwsku = None
|
|
name = None
|
|
deployment_id = None
|
|
cluster = None
|
|
d_subtype = None
|
|
|
|
for node in device:
|
|
if node.tag == str(QName(ns, "Address")):
|
|
lo_prefix = node.find(str(QName(ns2, "IPPrefix"))).text
|
|
elif node.tag == str(QName(ns, "AddressV6")):
|
|
lo_prefix_v6 = node.find(str(QName(ns2, "IPPrefix"))).text
|
|
elif node.tag == str(QName(ns, "ManagementAddress")):
|
|
mgmt_prefix = node.find(str(QName(ns2, "IPPrefix"))).text
|
|
elif node.tag == str(QName(ns, "ManagementAddressV6")):
|
|
mgmt_prefix_v6 = node.find(str(QName(ns2, "IPPrefix"))).text
|
|
elif node.tag == str(QName(ns, "Hostname")):
|
|
name = node.text
|
|
elif node.tag == str(QName(ns, "HwSku")):
|
|
hwsku = node.text
|
|
elif node.tag == str(QName(ns, "DeploymentId")):
|
|
deployment_id = node.text
|
|
elif node.tag == str(QName(ns, "ElementType")):
|
|
d_type = node.text
|
|
elif node.tag == str(QName(ns, "ClusterName")):
|
|
cluster = node.text
|
|
elif node.tag == str(QName(ns, "SubType")):
|
|
d_subtype = node.text
|
|
|
|
if d_type is None and str(QName(ns3, "type")) in device.attrib:
|
|
d_type = device.attrib[str(QName(ns3, "type"))]
|
|
|
|
return (lo_prefix, lo_prefix_v6, mgmt_prefix, mgmt_prefix_v6, name, hwsku, d_type, deployment_id, cluster, d_subtype)
|
|
|
|
|
|
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']
|
|
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 = {tag: {"bucket_size": LCM, "match_mode": "nexthop-based"}}
|
|
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, "NEIGH": NEIGH}
|
|
return fine_grained_content
|
|
|
|
|
|
def parse_png(png, hname, dpg_ecmp_content = None):
|
|
neighbors = {}
|
|
devices = {}
|
|
console_dev = ''
|
|
console_port = ''
|
|
mgmt_dev = ''
|
|
mgmt_port = ''
|
|
port_speeds = {}
|
|
console_ports = {}
|
|
mux_cable_ports = {}
|
|
port_device_map = {}
|
|
png_ecmp_content = {}
|
|
FG_NHG_MEMBER = {}
|
|
FG_NHG = {}
|
|
NEIGH = {}
|
|
|
|
for child in png:
|
|
if child.tag == str(QName(ns, "DeviceInterfaceLinks")):
|
|
for link in child.findall(str(QName(ns, "DeviceLinkBase"))):
|
|
linktype = link.find(str(QName(ns, "ElementType"))).text
|
|
if linktype == "DeviceSerialLink":
|
|
enddevice = link.find(str(QName(ns, "EndDevice"))).text
|
|
endport = link.find(str(QName(ns, "EndPort"))).text
|
|
startdevice = link.find(str(QName(ns, "StartDevice"))).text
|
|
startport = link.find(str(QName(ns, "StartPort"))).text
|
|
baudrate = link.find(str(QName(ns, "Bandwidth"))).text
|
|
flowcontrol = 1 if link.find(str(QName(ns, "FlowControl"))) is not None and link.find(str(QName(ns, "FlowControl"))).text == 'true' else 0
|
|
if enddevice.lower() == hname.lower() and endport.isdigit():
|
|
console_ports[endport] = {
|
|
'remote_device': startdevice,
|
|
'baud_rate': baudrate,
|
|
'flow_control': flowcontrol
|
|
}
|
|
elif startport.isdigit():
|
|
console_ports[startport] = {
|
|
'remote_device': enddevice,
|
|
'baud_rate': baudrate,
|
|
'flow_control': flowcontrol
|
|
}
|
|
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" and linktype != "DeviceMgmtLink":
|
|
continue
|
|
|
|
enddevice = link.find(str(QName(ns, "EndDevice"))).text
|
|
endport = link.find(str(QName(ns, "EndPort"))).text
|
|
startdevice = link.find(str(QName(ns, "StartDevice"))).text
|
|
startport = link.find(str(QName(ns, "StartPort"))).text
|
|
bandwidth_node = link.find(str(QName(ns, "Bandwidth")))
|
|
bandwidth = bandwidth_node.text if bandwidth_node is not None else None
|
|
if enddevice.lower() == hname.lower():
|
|
if endport in port_alias_map:
|
|
endport = port_alias_map[endport]
|
|
if linktype != "DeviceMgmtLink":
|
|
neighbors[endport] = {'name': startdevice, 'port': startport}
|
|
if bandwidth:
|
|
port_speeds[endport] = bandwidth
|
|
elif startdevice.lower() == hname.lower():
|
|
if startport in port_alias_map:
|
|
startport = port_alias_map[startport]
|
|
if linktype != "DeviceMgmtLink":
|
|
neighbors[startport] = {'name': enddevice, 'port': endport}
|
|
if bandwidth:
|
|
port_speeds[startport] = bandwidth
|
|
|
|
if child.tag == str(QName(ns, "Devices")):
|
|
for device in child.findall(str(QName(ns, "Device"))):
|
|
(lo_prefix, lo_prefix_v6, mgmt_prefix, mgmt_prefix_v6, name, hwsku, d_type, deployment_id, cluster, d_subtype) = parse_device(device)
|
|
device_data = {}
|
|
if hwsku != None:
|
|
device_data['hwsku'] = hwsku
|
|
if cluster != None:
|
|
device_data['cluster'] = cluster
|
|
if deployment_id != None:
|
|
device_data['deployment_id'] = deployment_id
|
|
if lo_prefix != None:
|
|
device_data['lo_addr'] = lo_prefix
|
|
if lo_prefix_v6 != None:
|
|
device_data['lo_addr_v6'] = lo_prefix_v6
|
|
if mgmt_prefix != None:
|
|
device_data['mgmt_addr'] = mgmt_prefix
|
|
if mgmt_prefix_v6 != None:
|
|
device_data['mgmt_addr_v6'] = mgmt_prefix_v6
|
|
if d_type != None:
|
|
device_data['type'] = d_type
|
|
if d_subtype != None:
|
|
device_data['subtype'] = d_subtype
|
|
devices[name] = device_data
|
|
|
|
if child.tag == str(QName(ns, "DeviceInterfaceLinks")):
|
|
for if_link in child.findall(str(QName(ns, 'DeviceLinkBase'))):
|
|
if str(QName(ns3, "type")) in if_link.attrib:
|
|
link_type = if_link.attrib[str(QName(ns3, "type"))]
|
|
if link_type == 'DeviceSerialLink':
|
|
for node in if_link:
|
|
if node.tag == str(QName(ns, "EndPort")):
|
|
console_port = node.text.split()[-1]
|
|
elif node.tag == str(QName(ns, "EndDevice")):
|
|
console_dev = node.text
|
|
elif link_type == 'DeviceMgmtLink':
|
|
for node in if_link:
|
|
if node.tag == str(QName(ns, "EndPort")):
|
|
mgmt_port = node.text.split()[-1]
|
|
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":
|
|
intf_name = link.find(str(QName(ns, "EndPort"))).text
|
|
start_device = link.find(str(QName(ns, "StartDevice"))).text
|
|
if intf_name in port_alias_map:
|
|
intf_name = port_alias_map[intf_name]
|
|
|
|
mux_cable_ports[intf_name] = start_device
|
|
|
|
if dpg_ecmp_content and (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.update(fine_grained_content['FG_NHG'])
|
|
NEIGH.update(fine_grained_content['NEIGH'])
|
|
|
|
png_ecmp_content = {"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, png_ecmp_content)
|
|
|
|
|
|
def parse_asic_external_link(link, asic_name, hostname):
|
|
neighbors = {}
|
|
port_speeds = {}
|
|
enddevice = link.find(str(QName(ns, "EndDevice"))).text
|
|
endport = link.find(str(QName(ns, "EndPort"))).text
|
|
startdevice = link.find(str(QName(ns, "StartDevice"))).text
|
|
startport = link.find(str(QName(ns, "StartPort"))).text
|
|
bandwidth_node = link.find(str(QName(ns, "Bandwidth")))
|
|
bandwidth = bandwidth_node.text if bandwidth_node is not None else None
|
|
# if chassis internal is false, the interface name will be
|
|
# interface alias which should be converted to asic port name
|
|
if (enddevice.lower() == hostname.lower()):
|
|
if endport in port_alias_asic_map:
|
|
endport = port_alias_asic_map[endport]
|
|
neighbors[port_alias_map[endport]] = {'name': startdevice, 'port': startport}
|
|
if bandwidth:
|
|
port_speeds[port_alias_map[endport]] = bandwidth
|
|
elif (startdevice.lower() == hostname.lower()):
|
|
if startport in port_alias_asic_map:
|
|
startport = port_alias_asic_map[startport]
|
|
neighbors[port_alias_map[startport]] = {'name': enddevice, 'port': endport}
|
|
if bandwidth:
|
|
port_speeds[port_alias_map[startport]] = bandwidth
|
|
|
|
return neighbors, port_speeds
|
|
|
|
def parse_asic_internal_link(link, asic_name, hostname):
|
|
neighbors = {}
|
|
port_speeds = {}
|
|
enddevice = link.find(str(QName(ns, "EndDevice"))).text
|
|
endport = link.find(str(QName(ns, "EndPort"))).text
|
|
startdevice = link.find(str(QName(ns, "StartDevice"))).text
|
|
startport = link.find(str(QName(ns, "StartPort"))).text
|
|
bandwidth_node = link.find(str(QName(ns, "Bandwidth")))
|
|
bandwidth = bandwidth_node.text if bandwidth_node is not None else None
|
|
if ((enddevice.lower() == asic_name.lower()) and
|
|
(startdevice.lower() != hostname.lower())):
|
|
if endport in port_alias_map:
|
|
endport = port_alias_map[endport]
|
|
neighbors[endport] = {'name': startdevice, 'port': startport}
|
|
if bandwidth:
|
|
port_speeds[endport] = bandwidth
|
|
elif ((startdevice.lower() == asic_name.lower()) and
|
|
(enddevice.lower() != hostname.lower())):
|
|
if startport in port_alias_map:
|
|
startport = port_alias_map[startport]
|
|
neighbors[startport] = {'name': enddevice, 'port': endport}
|
|
if bandwidth:
|
|
port_speeds[startport] = bandwidth
|
|
|
|
return neighbors, port_speeds
|
|
|
|
def parse_asic_png(png, asic_name, hostname):
|
|
neighbors = {}
|
|
devices = {}
|
|
port_speeds = {}
|
|
for child in png:
|
|
if child.tag == str(QName(ns, "DeviceInterfaceLinks")):
|
|
for link in child.findall(str(QName(ns, "DeviceLinkBase"))):
|
|
# Chassis internal node is used in multi-asic device or chassis minigraph
|
|
# where the minigraph will contain the internal asic connectivity and
|
|
# external neighbor information. The ChassisInternal node will be used to
|
|
# determine if the link is internal to the device or chassis.
|
|
chassis_internal_node = link.find(str(QName(ns, "ChassisInternal")))
|
|
chassis_internal = chassis_internal_node.text if chassis_internal_node is not None else "false"
|
|
|
|
# If the link is an external link include the external neighbor
|
|
# information in ASIC ports table
|
|
if chassis_internal.lower() == "false":
|
|
ext_neighbors, ext_port_speeds = parse_asic_external_link(link, asic_name, hostname)
|
|
neighbors.update(ext_neighbors)
|
|
port_speeds.update(ext_port_speeds)
|
|
else:
|
|
int_neighbors, int_port_speeds = parse_asic_internal_link(link, asic_name, hostname)
|
|
neighbors.update(int_neighbors)
|
|
port_speeds.update(int_port_speeds)
|
|
|
|
if child.tag == str(QName(ns, "Devices")):
|
|
for device in child.findall(str(QName(ns, "Device"))):
|
|
(lo_prefix, lo_prefix_v6, mgmt_prefix, mgmt_prefix_v6, name, hwsku, d_type, deployment_id, cluster, _) = parse_device(device)
|
|
device_data = {}
|
|
if hwsku != None:
|
|
device_data['hwsku'] = hwsku
|
|
if cluster != None:
|
|
device_data['cluster'] = cluster
|
|
if deployment_id != None:
|
|
device_data['deployment_id'] = deployment_id
|
|
if lo_prefix != None:
|
|
device_data['lo_addr'] = lo_prefix
|
|
if lo_prefix_v6 != None:
|
|
device_data['lo_addr_v6'] = lo_prefix_v6
|
|
if mgmt_prefix != None:
|
|
device_data['mgmt_addr'] = mgmt_prefix
|
|
if mgmt_prefix_v6 != None:
|
|
device_data['mgmt_addr_v6'] = mgmt_prefix_v6
|
|
if d_type != None:
|
|
device_data['type'] = d_type
|
|
devices[name] = device_data
|
|
|
|
return (neighbors, devices, port_speeds)
|
|
|
|
|
|
def parse_loopback_intf(child):
|
|
lointfs = child.find(str(QName(ns, "LoopbackIPInterfaces")))
|
|
lo_intfs = {}
|
|
for lointf in lointfs.findall(str(QName(ns1, "LoopbackIPInterface"))):
|
|
intfname = lointf.find(str(QName(ns, "AttachTo"))).text
|
|
ipprefix = lointf.find(str(QName(ns1, "PrefixStr"))).text
|
|
lo_intfs[(intfname, ipprefix)] = {}
|
|
return lo_intfs
|
|
|
|
|
|
def parse_dpg(dpg, hname):
|
|
aclintfs = {}
|
|
mgmtintfs = {}
|
|
subintfs = None
|
|
intfs= {}
|
|
lo_intfs= {}
|
|
mvrf= {}
|
|
mgmt_intf= {}
|
|
voq_inband_intfs= {}
|
|
vlans= {}
|
|
vlan_members= {}
|
|
dhcp_relay_table= {}
|
|
pcs= {}
|
|
pc_members= {}
|
|
acls= {}
|
|
acl_table_types = {}
|
|
vni= {}
|
|
dpg_ecmp_content= {}
|
|
static_routes= {}
|
|
tunnelintfs = defaultdict(dict)
|
|
tunnelintfs_qos_remap_config = defaultdict(dict)
|
|
|
|
for child in dpg:
|
|
"""
|
|
In Multi-NPU platforms the acl intfs are defined only for the host not for individual asic.
|
|
There is just one aclintf node in the minigraph
|
|
Get the aclintfs node first.
|
|
"""
|
|
if not aclintfs and child.find(str(QName(ns, "AclInterfaces"))) is not None and child.find(str(QName(ns, "AclInterfaces"))).findall(str(QName(ns, "AclInterface"))):
|
|
aclintfs = child.find(str(QName(ns, "AclInterfaces"))).findall(str(QName(ns, "AclInterface")))
|
|
"""
|
|
In Multi-NPU platforms the mgmt intfs are defined only for the host not for individual asic
|
|
There is just one mgmtintf node in the minigraph
|
|
Get the mgmtintfs node first. We need mgmt intf to get mgmt ip in per asic dockers.
|
|
"""
|
|
if not mgmtintfs and child.find(str(QName(ns, "ManagementIPInterfaces"))) is not None and child.find(str(QName(ns, "ManagementIPInterfaces"))).findall(str(QName(ns1, "ManagementIPInterface"))):
|
|
mgmtintfs = child.find(str(QName(ns, "ManagementIPInterfaces"))).findall(str(QName(ns1, "ManagementIPInterface")))
|
|
hostname = child.find(str(QName(ns, "Hostname")))
|
|
if hostname.text.lower() != hname.lower():
|
|
continue
|
|
|
|
vni = vni_default
|
|
vni_element = child.find(str(QName(ns, "VNI")))
|
|
if vni_element != None:
|
|
if vni_element.text.isdigit():
|
|
vni = int(vni_element.text)
|
|
else:
|
|
print("VNI must be an integer (use default VNI %d instead)" % vni_default, file=sys.stderr)
|
|
|
|
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)
|
|
|
|
subintfs = child.find(str(QName(ns, "SubInterfaces")))
|
|
if subintfs is not None:
|
|
for subintf in subintfs.findall(str(QName(ns, "SubInterface"))):
|
|
intfalias = subintf.find(str(QName(ns, "AttachTo"))).text
|
|
intfname = port_alias_map.get(intfalias, intfalias)
|
|
ipprefix = subintf.find(str(QName(ns, "Prefix"))).text
|
|
subintfvlan = subintf.find(str(QName(ns, "Vlan"))).text
|
|
subintfname = intfname + VLAN_SUB_INTERFACE_SEPARATOR + subintfvlan
|
|
intfs[(subintfname, ipprefix)] = {}
|
|
|
|
mvrfConfigs = child.find(str(QName(ns, "MgmtVrfConfigs")))
|
|
mvrf = {}
|
|
if mvrfConfigs != None:
|
|
mv = mvrfConfigs.find(str(QName(ns1, "MgmtVrfGlobal")))
|
|
if mv != None:
|
|
mvrf_en_flag = mv.find(str(QName(ns, "mgmtVrfEnabled"))).text
|
|
mvrf["vrf_global"] = {"mgmtVrfEnabled": mvrf_en_flag}
|
|
|
|
mgmt_intf = {}
|
|
for mgmtintf in mgmtintfs:
|
|
intfname = mgmtintf.find(str(QName(ns, "AttachTo"))).text
|
|
ipprefix = mgmtintf.find(str(QName(ns1, "PrefixStr"))).text
|
|
mgmtipn = ipaddress.ip_network(UNICODE_TYPE(ipprefix), False)
|
|
gwaddr = ipaddress.ip_address(next(mgmtipn.hosts()))
|
|
mgmt_intf[(intfname, ipprefix)] = {'gwaddr': gwaddr}
|
|
|
|
voqinbandintfs = child.find(str(QName(ns, "VoqInbandInterfaces")))
|
|
voq_inband_intfs = {}
|
|
if voqinbandintfs:
|
|
for voqintf in voqinbandintfs.findall(str(QName(ns1, "VoqInbandInterface"))):
|
|
intfname = voqintf.find(str(QName(ns, "Name"))).text
|
|
intftype = voqintf.find(str(QName(ns, "Type"))).text
|
|
ipprefix = voqintf.find(str(QName(ns1, "PrefixStr"))).text
|
|
if intfname not in voq_inband_intfs:
|
|
voq_inband_intfs[intfname] = {'inband_type': intftype}
|
|
voq_inband_intfs["%s|%s" % (intfname, ipprefix)] = {}
|
|
|
|
pcintfs = child.find(str(QName(ns, "PortChannelInterfaces")))
|
|
pc_intfs = []
|
|
pcs = {}
|
|
pc_members = {}
|
|
intfs_inpc = [] # List to hold all the LAG member interfaces
|
|
for pcintf in pcintfs.findall(str(QName(ns, "PortChannel"))):
|
|
pcintfname = pcintf.find(str(QName(ns, "Name"))).text
|
|
pcintfmbr = pcintf.find(str(QName(ns, "AttachTo"))).text
|
|
pcmbr_list = pcintfmbr.split(';')
|
|
pc_intfs.append(pcintfname)
|
|
for i, member in enumerate(pcmbr_list):
|
|
pcmbr_list[i] = port_alias_map.get(member, member)
|
|
intfs_inpc.append(pcmbr_list[i])
|
|
pc_members[(pcintfname, pcmbr_list[i])] = {}
|
|
if pcintf.find(str(QName(ns, "Fallback"))) != None:
|
|
pcs[pcintfname] = {'fallback': pcintf.find(str(QName(ns, "Fallback"))).text, 'min_links': str(int(math.ceil(len() * 0.75))), 'lacp_key': 'auto'}
|
|
else:
|
|
pcs[pcintfname] = {'min_links': str(int(math.ceil(len(pcmbr_list) * 0.75))), 'lacp_key': 'auto' }
|
|
port_nhipv4_map = {}
|
|
port_nhipv6_map = {}
|
|
nhg_int = ""
|
|
nhportlist = []
|
|
dpg_ecmp_content = {}
|
|
static_routes = {}
|
|
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
|
|
elif ipnh.find(str(QName(ns, "Type"))).text == 'StaticRoute':
|
|
prefix = ipnh.find(str(QName(ns, "Address"))).text
|
|
ifname = []
|
|
nexthop = []
|
|
for nexthop_tuple in ipnh.find(str(QName(ns, "AttachTo"))).text.split(";"):
|
|
ifname.append(nexthop_tuple.split(",")[0])
|
|
nexthop.append(nexthop_tuple.split(",")[1])
|
|
if ipnh.find(str(QName(ns, "Advertise"))):
|
|
advertise = ipnh.find(str(QName(ns, "Advertise"))).text
|
|
else:
|
|
advertise = "false"
|
|
if '/' not in prefix:
|
|
if ":" in prefix:
|
|
prefix = prefix + "/128"
|
|
else:
|
|
prefix = prefix + "/32"
|
|
static_routes[prefix] = {'nexthop': ",".join(nexthop), 'ifname': ",".join(ifname), 'advertise': advertise}
|
|
|
|
if port_nhipv4_map and port_nhipv6_map:
|
|
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]
|
|
|
|
ipv4_content = {"port_nhip_map": port_nhipv4_map, "nhg_int": nhg_int}
|
|
ipv6_content = {"port_nhip_map": port_nhipv6_map, "nhg_int": nhg_int}
|
|
dpg_ecmp_content['ipv4'] = ipv4_content
|
|
dpg_ecmp_content['ipv6'] = ipv6_content
|
|
|
|
vlanintfs = child.find(str(QName(ns, "VlanInterfaces")))
|
|
vlans = {}
|
|
vlan_members = {}
|
|
vlan_member_list = {}
|
|
dhcp_relay_table = {}
|
|
# Dict: vlan member (port/PortChannel) -> set of VlanID, in which the member if an untagged vlan member
|
|
untagged_vlan_mbr = defaultdict(set)
|
|
for vintf in vlanintfs.findall(str(QName(ns, "VlanInterface"))):
|
|
vlanid = vintf.find(str(QName(ns, "VlanID"))).text
|
|
vlantype = vintf.find(str(QName(ns, "Type")))
|
|
if vlantype is None:
|
|
vlantype_name = ""
|
|
else:
|
|
vlantype_name = vlantype.text
|
|
vintfmbr = vintf.find(str(QName(ns, "AttachTo"))).text
|
|
vmbr_list = vintfmbr.split(';')
|
|
if vlantype_name != "Tagged":
|
|
for member in vmbr_list:
|
|
untagged_vlan_mbr[member].add(vlanid)
|
|
for vintf in vlanintfs.findall(str(QName(ns, "VlanInterface"))):
|
|
vintfname = vintf.find(str(QName(ns, "Name"))).text
|
|
vlanid = vintf.find(str(QName(ns, "VlanID"))).text
|
|
vintfmbr = vintf.find(str(QName(ns, "AttachTo"))).text
|
|
vlantype = vintf.find(str(QName(ns, "Type")))
|
|
if vlantype is None:
|
|
vlantype_name = ""
|
|
else:
|
|
vlantype_name = vlantype.text
|
|
vmbr_list = vintfmbr.split(';')
|
|
for i, member in enumerate(vmbr_list):
|
|
vmbr_list[i] = port_alias_map.get(member, member)
|
|
sonic_vlan_member_name = "Vlan%s" % (vlanid)
|
|
if vlantype_name == "Tagged":
|
|
vlan_members[(sonic_vlan_member_name, vmbr_list[i])] = {'tagging_mode': 'tagged'}
|
|
elif len(untagged_vlan_mbr[member]) > 1:
|
|
vlan_members[(sonic_vlan_member_name, vmbr_list[i])] = {'tagging_mode': 'tagged'}
|
|
else:
|
|
vlan_members[(sonic_vlan_member_name, vmbr_list[i])] = {'tagging_mode': 'untagged'}
|
|
|
|
vlan_attributes = {'vlanid': vlanid}
|
|
dhcp_attributes = {}
|
|
|
|
# If this VLAN requires a DHCP relay agent, it will contain a <DhcpRelays> element
|
|
# containing a list of DHCP server IPs
|
|
vintf_node = vintf.find(str(QName(ns, "DhcpRelays")))
|
|
if vintf_node is not None and vintf_node.text is not None:
|
|
vintfdhcpservers = vintf_node.text
|
|
vdhcpserver_list = vintfdhcpservers.split(';')
|
|
vlan_attributes['dhcp_servers'] = vdhcpserver_list
|
|
|
|
vintf_node = vintf.find(str(QName(ns, "Dhcpv6Relays")))
|
|
if vintf_node is not None and vintf_node.text is not None:
|
|
vintfdhcpservers = vintf_node.text
|
|
vdhcpserver_list = vintfdhcpservers.split(';')
|
|
vlan_attributes['dhcpv6_servers'] = vdhcpserver_list
|
|
dhcp_attributes['dhcpv6_servers'] = vdhcpserver_list
|
|
sonic_vlan_member_name = "Vlan%s" % (vlanid)
|
|
dhcp_relay_table[sonic_vlan_member_name] = dhcp_attributes
|
|
|
|
vlanmac = vintf.find(str(QName(ns, "MacAddress")))
|
|
if vlanmac is not None and vlanmac.text is not None:
|
|
vlan_attributes['mac'] = vlanmac.text
|
|
|
|
sonic_vlan_name = "Vlan%s" % vlanid
|
|
if sonic_vlan_name != vintfname:
|
|
vlan_attributes['alias'] = vintfname
|
|
vlans[sonic_vlan_name] = vlan_attributes
|
|
vlan_member_list[sonic_vlan_name] = vmbr_list
|
|
|
|
for aclintf in aclintfs:
|
|
if aclintf.find(str(QName(ns, "InAcl"))) is not None:
|
|
aclname = aclintf.find(str(QName(ns, "InAcl"))).text.upper().replace(" ", "_").replace("-", "_")
|
|
stage = "ingress"
|
|
elif aclintf.find(str(QName(ns, "OutAcl"))) is not None:
|
|
aclname = aclintf.find(str(QName(ns, "OutAcl"))).text.upper().replace(" ", "_").replace("-", "_")
|
|
stage = "egress"
|
|
else:
|
|
sys.exit("Error: 'AclInterface' must contain either an 'InAcl' or 'OutAcl' subelement.")
|
|
aclattach = aclintf.find(str(QName(ns, "AttachTo"))).text.split(';')
|
|
acl_intfs = []
|
|
is_bmc_data = False
|
|
is_bmc_data_v6 = False
|
|
is_mirror = False
|
|
is_mirror_v6 = False
|
|
is_mirror_dscp = False
|
|
use_port_alias = True
|
|
|
|
# Walk through all interface names/alias to determine whether the input string is
|
|
# port name or alias.We need this logic because there can be duplicaitons in port alias
|
|
# and port names
|
|
# The input port name/alias can be either port_name or port_alias. A mix of name and alias is not accepted
|
|
port_name_count = 0
|
|
port_alias_count = 0
|
|
total_count = 0
|
|
for member in aclattach:
|
|
member = member.strip()
|
|
if member in pcs or \
|
|
member in vlans or \
|
|
member.lower().startswith('erspan') or \
|
|
member.lower().startswith('egress_erspan') or \
|
|
member.lower().startswith('erspan_dscp'):
|
|
continue
|
|
total_count += 1
|
|
if member in port_alias_map:
|
|
port_alias_count += 1
|
|
if member in port_names_map:
|
|
port_name_count += 1
|
|
# All inputs are port alias
|
|
if port_alias_count == total_count:
|
|
use_port_alias = True
|
|
# All inputs are port name
|
|
elif port_name_count == total_count:
|
|
use_port_alias = False
|
|
# There are both port alias and port name, then port alias is preferred to keep the behavior not changed
|
|
else:
|
|
use_port_alias = True
|
|
# For CTRLPLANE ACL, both counters are 0
|
|
if (port_alias_count != 0) and (port_name_count != 0):
|
|
print("Warning: The given port name for ACL " + aclname + " is inconsistent. It must be either port name or alias ", file=sys.stderr)
|
|
|
|
# TODO: Ensure that acl_intfs will only ever contain front-panel interfaces (e.g.,
|
|
# maybe we should explicity ignore management and loopback interfaces?) because we
|
|
# decide an ACL is a Control Plane ACL if acl_intfs is empty below.
|
|
for member in aclattach:
|
|
member = member.strip()
|
|
if member in pcs:
|
|
# If try to attach ACL to a LAG interface then we shall add the LAG to
|
|
# to acl_intfs directly instead of break it into member ports, ACL attach
|
|
# to LAG will be applied to all the LAG members internally by SAI/SDK
|
|
acl_intfs.append(member)
|
|
elif member in vlans:
|
|
# For egress ACL attaching to vlan, we break them into vlan members
|
|
if stage == "egress":
|
|
acl_intfs.extend(vlan_member_list[member])
|
|
else:
|
|
acl_intfs.append(member)
|
|
elif use_port_alias and (member in port_alias_map):
|
|
acl_intfs.append(port_alias_map[member])
|
|
# Give a warning if trying to attach ACL to a LAG member interface, correct way is to attach ACL to the LAG interface
|
|
if port_alias_map[member] in intfs_inpc:
|
|
print("Warning: ACL " + aclname + " is attached to a LAG member interface " + port_alias_map[member] + ", instead of LAG interface", file=sys.stderr)
|
|
elif (not use_port_alias) and (member in port_names_map):
|
|
acl_intfs.append(member)
|
|
if member in intfs_inpc:
|
|
print("Warning: ACL " + aclname + " is attached to a LAG member interface " + member + ", instead of LAG interface", file=sys.stderr)
|
|
elif member.lower().startswith('erspan') or member.lower().startswith('egress_erspan') or member.lower().startswith('erspan_dscp'):
|
|
if 'dscp' in member.lower():
|
|
is_mirror_dscp = True
|
|
elif member.lower().startswith('erspanv6') or member.lower().startswith('egress_erspanv6'):
|
|
is_mirror_v6 = True
|
|
else:
|
|
is_mirror = True
|
|
# Erspan session will be attached to all front panel ports
|
|
# initially. If panel ports is a member port of LAG, then
|
|
# the LAG will be added to acl table instead of the panel
|
|
# ports. Non-active ports will be removed from this list
|
|
# later after the rest of the minigraph has been parsed.
|
|
acl_intfs = pc_intfs[:]
|
|
for panel_port in port_alias_map.values():
|
|
# because of port_alias_asic_map we can have duplicate in port_alias_map
|
|
# so check if already present do not add
|
|
if panel_port not in intfs_inpc and panel_port not in acl_intfs:
|
|
acl_intfs.append(panel_port)
|
|
break
|
|
if aclintf.find(str(QName(ns, "Type"))) is not None and aclintf.find(str(QName(ns, "Type"))).text.upper() == "BMCDATA":
|
|
if 'v6' in aclname.lower():
|
|
is_bmc_data_v6 = True
|
|
acl_table_types['BMCDATAV6'] = acl_table_type_defination['BMCDATAV6']
|
|
else:
|
|
is_bmc_data = True
|
|
acl_table_types['BMCDATA'] = acl_table_type_defination['BMCDATA']
|
|
# if acl is classified as mirror (erpsan) or acl interface
|
|
# are binded then do not classify as Control plane.
|
|
# For multi-asic platforms it's possible there is no
|
|
# interface are binded to everflow in host namespace.
|
|
if acl_intfs or is_mirror_v6 or is_mirror or is_mirror_dscp:
|
|
# Remove duplications
|
|
dedup_intfs = []
|
|
for intf in acl_intfs:
|
|
if intf not in dedup_intfs:
|
|
dedup_intfs.append(intf)
|
|
|
|
acls[aclname] = {'policy_desc': aclname,
|
|
'stage': stage,
|
|
'ports': dedup_intfs}
|
|
if is_mirror:
|
|
acls[aclname]['type'] = 'MIRROR'
|
|
elif is_mirror_v6:
|
|
acls[aclname]['type'] = 'MIRRORV6'
|
|
elif is_mirror_dscp:
|
|
acls[aclname]['type'] = 'MIRROR_DSCP'
|
|
elif is_bmc_data:
|
|
acls[aclname]['type'] = 'BMCDATA'
|
|
elif is_bmc_data_v6:
|
|
acls[aclname]['type'] = 'BMCDATAV6'
|
|
else:
|
|
acls[aclname]['type'] = 'L3V6' if 'v6' in aclname.lower() else 'L3'
|
|
else:
|
|
# This ACL has no interfaces to attach to -- consider this a control plane ACL
|
|
try:
|
|
aclservice = aclintf.find(str(QName(ns, "Type"))).text
|
|
|
|
# If we already have an ACL with this name and this ACL is bound to a different service,
|
|
# append the service to our list of services
|
|
if aclname in acls:
|
|
if acls[aclname]['type'] != 'CTRLPLANE':
|
|
print("Warning: ACL '%s' type mismatch. Not updating ACL." % aclname, file=sys.stderr)
|
|
elif acls[aclname]['services'] == aclservice:
|
|
print("Warning: ACL '%s' already contains service '%s'. Not updating ACL." % (aclname, aclservice), file=sys.stderr)
|
|
else:
|
|
acls[aclname]['services'].append(aclservice)
|
|
else:
|
|
acls[aclname] = {'policy_desc': aclname,
|
|
'type': 'CTRLPLANE',
|
|
'stage': stage,
|
|
'services': [aclservice]}
|
|
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",
|
|
"ecn_mode": "EcnDecapsulationMode",
|
|
"dscp_mode": "DifferentiatedServicesCodePointMode",
|
|
"ttl_mode": "TtlMode"}
|
|
|
|
tunnel_qos_remap_table_key_to_mg_key_map = {
|
|
"decap_dscp_to_tc_map": "DecapDscpToTcMap",
|
|
"decap_tc_to_pg_map": "DecapTcToPgMap",
|
|
"encap_tc_to_queue_map": "EncapTcToQueueMap",
|
|
"encap_tc_to_dscp_map": "EncapTcToDscpMap"}
|
|
|
|
for mg_tunnel in mg_tunnels.findall(str(QName(ns, "TunnelInterface"))):
|
|
tunnel_type = mg_tunnel.attrib["Type"]
|
|
tunnel_name = mg_tunnel.attrib["Name"]
|
|
tunnelintfs[tunnel_type][tunnel_name] = {
|
|
"tunnel_type": mg_tunnel.attrib["Type"].upper(),
|
|
}
|
|
|
|
for table_key, mg_key in table_key_to_mg_key_map.items():
|
|
# If the minigraph has the key, add the corresponding config DB key to the table
|
|
if mg_key in mg_tunnel.attrib:
|
|
tunnelintfs[tunnel_type][tunnel_name][table_key] = mg_tunnel.attrib[mg_key]
|
|
|
|
tunnelintfs_qos_remap_config[tunnel_type][tunnel_name] = {
|
|
"tunnel_type": mg_tunnel.attrib["Type"].upper(),
|
|
}
|
|
|
|
for table_key, mg_key in tunnel_qos_remap_table_key_to_mg_key_map.items():
|
|
if mg_key in mg_tunnel.attrib:
|
|
tunnelintfs_qos_remap_config[tunnel_type][tunnel_name][table_key] = mg_tunnel.attrib[mg_key]
|
|
return intfs, lo_intfs, mvrf, mgmt_intf, voq_inband_intfs, vlans, vlan_members, dhcp_relay_table, pcs, pc_members, acls, acl_table_types, vni, tunnelintfs, dpg_ecmp_content, static_routes, tunnelintfs_qos_remap_config
|
|
return intfs, lo_intfs, mvrf, mgmt_intf, voq_inband_intfs, vlans, vlan_members, dhcp_relay_table, pcs, pc_members, acls, acl_table_types, vni, tunnelintfs, dpg_ecmp_content, static_routes, tunnelintfs_qos_remap_config
|
|
|
|
|
|
def parse_host_loopback(dpg, hname):
|
|
for child in dpg:
|
|
hostname = child.find(str(QName(ns, "Hostname")))
|
|
if hostname.text.lower() != hname.lower():
|
|
continue
|
|
lo_intfs = parse_loopback_intf(child)
|
|
return lo_intfs
|
|
|
|
|
|
def parse_cpg(cpg, hname, local_devices=[]):
|
|
bgp_sessions = {}
|
|
bgp_internal_sessions = {}
|
|
bgp_voq_chassis_sessions = {}
|
|
myasn = None
|
|
bgp_peers_with_range = {}
|
|
for child in cpg:
|
|
tag = child.tag
|
|
if tag == str(QName(ns, "PeeringSessions")):
|
|
for session in child.findall(str(QName(ns, "BGPSession"))):
|
|
start_router = session.find(str(QName(ns, "StartRouter"))).text
|
|
start_peer = session.find(str(QName(ns, "StartPeer"))).text
|
|
end_router = session.find(str(QName(ns, "EndRouter"))).text
|
|
end_peer = session.find(str(QName(ns, "EndPeer"))).text
|
|
rrclient = 1 if session.find(str(QName(ns, "RRClient"))) is not None else 0
|
|
if session.find(str(QName(ns, "HoldTime"))) is not None:
|
|
holdtime = session.find(str(QName(ns, "HoldTime"))).text
|
|
else:
|
|
holdtime = 180
|
|
if session.find(str(QName(ns, "KeepAliveTime"))) is not None:
|
|
keepalive = session.find(str(QName(ns, "KeepAliveTime"))).text
|
|
else:
|
|
keepalive = 60
|
|
nhopself = 1 if session.find(str(QName(ns, "NextHopSelf"))) is not None else 0
|
|
|
|
# choose the right table and admin_status for the peer
|
|
chassis_internal_ibgp = session.find(str(QName(ns, "ChassisInternal")))
|
|
if chassis_internal_ibgp is not None and chassis_internal_ibgp.text == "voq":
|
|
table = bgp_voq_chassis_sessions
|
|
admin_status = 'up'
|
|
elif chassis_internal_ibgp is not None and chassis_internal_ibgp.text == "chassis-packet":
|
|
table = bgp_internal_sessions
|
|
admin_status = 'up'
|
|
elif end_router.lower() in local_devices and start_router.lower() in local_devices:
|
|
table = bgp_internal_sessions
|
|
admin_status = 'up'
|
|
else:
|
|
table = bgp_sessions
|
|
admin_status = None
|
|
|
|
if end_router.lower() == hname.lower():
|
|
table[start_peer.lower()] = {
|
|
'name': start_router,
|
|
'local_addr': end_peer.lower(),
|
|
'rrclient': rrclient,
|
|
'holdtime': holdtime,
|
|
'keepalive': keepalive,
|
|
'nhopself': nhopself
|
|
}
|
|
if admin_status:
|
|
table[start_peer.lower()]['admin_status'] = admin_status
|
|
elif start_router.lower() == hname.lower():
|
|
table[end_peer.lower()] = {
|
|
'name': end_router,
|
|
'local_addr': start_peer.lower(),
|
|
'rrclient': rrclient,
|
|
'holdtime': holdtime,
|
|
'keepalive': keepalive,
|
|
'nhopself': nhopself
|
|
}
|
|
if admin_status:
|
|
table[end_peer.lower()]['admin_status'] = admin_status
|
|
elif child.tag == str(QName(ns, "Routers")):
|
|
for router in child.findall(str(QName(ns1, "BGPRouterDeclaration"))):
|
|
asn = router.find(str(QName(ns1, "ASN"))).text
|
|
hostname = router.find(str(QName(ns1, "Hostname"))).text
|
|
if hostname.lower() == hname.lower():
|
|
myasn = asn
|
|
peers = router.find(str(QName(ns1, "Peers")))
|
|
for bgpPeer in peers.findall(str(QName(ns, "BGPPeer"))):
|
|
addr = bgpPeer.find(str(QName(ns, "Address"))).text
|
|
if bgpPeer.find(str(QName(ns1, "PeersRange"))) is not None: # FIXME: is better to check for type BGPPeerPassive
|
|
name = bgpPeer.find(str(QName(ns1, "Name"))).text
|
|
ip_range = bgpPeer.find(str(QName(ns1, "PeersRange"))).text
|
|
ip_range_group = ip_range.split(';') if ip_range and ip_range != "" else []
|
|
bgp_peers_with_range[name] = {
|
|
'name': name,
|
|
'ip_range': ip_range_group
|
|
}
|
|
if bgpPeer.find(str(QName(ns, "Address"))) is not None:
|
|
bgp_peers_with_range[name]['src_address'] = bgpPeer.find(str(QName(ns, "Address"))).text
|
|
if bgpPeer.find(str(QName(ns1, "PeerAsn"))) is not None:
|
|
bgp_peers_with_range[name]['peer_asn'] = bgpPeer.find(str(QName(ns1, "PeerAsn"))).text
|
|
else:
|
|
for peer in bgp_sessions:
|
|
bgp_session = bgp_sessions[peer]
|
|
if hostname.lower() == bgp_session['name'].lower():
|
|
bgp_session['asn'] = asn
|
|
for peer in bgp_internal_sessions:
|
|
bgp_internal_session = bgp_internal_sessions[peer]
|
|
if hostname.lower() == bgp_internal_session['name'].lower():
|
|
bgp_internal_session['asn'] = asn
|
|
for peer in bgp_voq_chassis_sessions:
|
|
bgp_session = bgp_voq_chassis_sessions[peer]
|
|
if hostname.lower() == bgp_session['name'].lower():
|
|
bgp_session['asn'] = asn
|
|
|
|
bgp_monitors = { key: bgp_sessions[key] for key in bgp_sessions if 'asn' in bgp_sessions[key] and bgp_sessions[key]['name'] == 'BGPMonitor' }
|
|
def filter_bad_asn(table):
|
|
return { key: table[key] for key in table if 'asn' in table[key] and int(table[key]['asn']) != 0 }
|
|
bgp_sessions = filter_bad_asn(bgp_sessions)
|
|
bgp_internal_sessions = filter_bad_asn(bgp_internal_sessions)
|
|
bgp_voq_chassis_sessions = filter_bad_asn(bgp_voq_chassis_sessions)
|
|
|
|
return bgp_sessions, bgp_internal_sessions, bgp_voq_chassis_sessions, myasn, bgp_peers_with_range, bgp_monitors
|
|
|
|
|
|
def parse_meta(meta, hname):
|
|
syslog_servers = []
|
|
dhcp_servers = []
|
|
dhcpv6_servers = []
|
|
ntp_servers = []
|
|
tacacs_servers = []
|
|
mgmt_routes = []
|
|
erspan_dst = []
|
|
deployment_id = None
|
|
region = None
|
|
cloudtype = None
|
|
resource_type = None
|
|
downstream_subrole = None
|
|
switch_id = None
|
|
switch_type = None
|
|
max_cores = None
|
|
kube_data = {}
|
|
macsec_profile = {}
|
|
redundancy_type = None
|
|
downstream_redundancy_types = None
|
|
qos_profile = None
|
|
rack_mgmt_map = None
|
|
|
|
device_metas = meta.find(str(QName(ns, "Devices")))
|
|
for device in device_metas.findall(str(QName(ns1, "DeviceMetadata"))):
|
|
if device.find(str(QName(ns1, "Name"))).text.lower() == hname.lower():
|
|
properties = device.find(str(QName(ns1, "Properties")))
|
|
for device_property in properties.findall(str(QName(ns1, "DeviceProperty"))):
|
|
name = device_property.find(str(QName(ns1, "Name"))).text
|
|
value = device_property.find(str(QName(ns1, "Value"))).text
|
|
value_group = value.strip().split(';') if value and value != "" else []
|
|
if name == "DhcpResources":
|
|
dhcp_servers = value_group
|
|
elif name == "NtpResources":
|
|
ntp_servers = value_group
|
|
elif name == "SyslogResources":
|
|
syslog_servers = value_group
|
|
elif name == "TacacsServer":
|
|
tacacs_servers = value_group
|
|
elif name == "ForcedMgmtRoutes":
|
|
mgmt_routes = value_group
|
|
elif name == "ErspanDestinationIpv4":
|
|
erspan_dst = value_group
|
|
elif name == "DeploymentId":
|
|
deployment_id = value
|
|
elif name == "Region":
|
|
region = value
|
|
elif name == "CloudType":
|
|
cloudtype = value
|
|
elif name == "ResourceType":
|
|
resource_type = value
|
|
elif name == "DownStreamSubRole":
|
|
downstream_subrole = value
|
|
elif name == "SwitchId":
|
|
switch_id = value
|
|
elif name == "SwitchType":
|
|
switch_type = value
|
|
elif name == "MaxCores":
|
|
max_cores = value
|
|
elif name == "KubernetesEnabled":
|
|
kube_data["enable"] = value
|
|
elif name == "KubernetesServerIp":
|
|
kube_data["ip"] = value
|
|
elif name == 'MacSecProfile':
|
|
macsec_profile = parse_macsec_profile(value)
|
|
elif name == "RedundancyType":
|
|
redundancy_type = value
|
|
elif name == "DownstreamRedundancyTypes":
|
|
downstream_redundancy_types = value
|
|
elif name == "SonicQosProfile":
|
|
qos_profile = value
|
|
elif name == "RackMgmtMap":
|
|
rack_mgmt_map = value
|
|
return syslog_servers, dhcp_servers, dhcpv6_servers, ntp_servers, tacacs_servers, mgmt_routes, erspan_dst, deployment_id, region, cloudtype, resource_type, downstream_subrole, switch_id, switch_type, max_cores, kube_data, macsec_profile, downstream_redundancy_types, redundancy_type, qos_profile, rack_mgmt_map
|
|
|
|
|
|
def parse_linkmeta(meta, hname):
|
|
link = meta.find(str(QName(ns, "Link")))
|
|
linkmetas = {}
|
|
for linkmeta in link.findall(str(QName(ns1, "LinkMetadata"))):
|
|
port = None
|
|
fec_disabled = None
|
|
|
|
# Sample: ARISTA05T1:Ethernet1/33;switch-t0:fortyGigE0/4
|
|
key = linkmeta.find(str(QName(ns1, "Key"))).text
|
|
endpoints = key.split(';')
|
|
for endpoint in endpoints:
|
|
t = endpoint.split(':')
|
|
if len(t) == 2 and t[0].lower() == hname.lower():
|
|
port = t[1]
|
|
break
|
|
else:
|
|
# Cannot find a matching hname, something went wrong
|
|
continue
|
|
|
|
has_peer_switch = False
|
|
upper_tor_hostname = ''
|
|
lower_tor_hostname = ''
|
|
auto_negotiation = None
|
|
macsec_enabled = False
|
|
tx_power = None
|
|
laser_freq = None
|
|
properties = linkmeta.find(str(QName(ns1, "Properties")))
|
|
for device_property in properties.findall(str(QName(ns1, "DeviceProperty"))):
|
|
name = device_property.find(str(QName(ns1, "Name"))).text
|
|
value = device_property.find(str(QName(ns1, "Value"))).text
|
|
if name == "FECDisabled":
|
|
fec_disabled = value
|
|
elif name in [ "GeminiPeeringLink", "LibraPeeringLink" ]:
|
|
has_peer_switch = True
|
|
elif name == "UpperTOR":
|
|
upper_tor_hostname = value
|
|
elif name == "LowerTOR":
|
|
lower_tor_hostname = value
|
|
elif name == "AutoNegotiation":
|
|
auto_negotiation = value
|
|
elif name == "MacSecEnabled":
|
|
macsec_enabled = value
|
|
elif name == "TxPower":
|
|
tx_power = value
|
|
elif name == "Frequency":
|
|
laser_freq = value
|
|
|
|
linkmetas[port] = {}
|
|
if fec_disabled:
|
|
linkmetas[port]["FECDisabled"] = fec_disabled
|
|
if has_peer_switch:
|
|
if upper_tor_hostname == hname:
|
|
linkmetas[port]["PeerSwitch"] = lower_tor_hostname
|
|
else:
|
|
linkmetas[port]["PeerSwitch"] = upper_tor_hostname
|
|
if auto_negotiation:
|
|
linkmetas[port]["AutoNegotiation"] = auto_negotiation
|
|
if macsec_enabled:
|
|
linkmetas[port]["MacSecEnabled"] = macsec_enabled
|
|
if tx_power:
|
|
linkmetas[port]["tx_power"] = tx_power
|
|
# Convert the freq in GHz
|
|
if laser_freq:
|
|
linkmetas[port]["laser_freq"] = int(float(laser_freq)*1000)
|
|
return linkmetas
|
|
|
|
def parse_macsec_profile(val_string):
|
|
macsec_profile = {}
|
|
values = val_string.strip().split()
|
|
for val in values:
|
|
keys = val.strip().split('=')
|
|
if keys[0] == 'PrimaryKey':
|
|
macsec_profile['PrimaryKey'] = keys[1].strip('\"')
|
|
elif keys[0] == 'FallbackKey':
|
|
macsec_profile['FallbackKey'] = keys[1].strip('\"')
|
|
|
|
return macsec_profile
|
|
|
|
def parse_asic_meta(meta, hname):
|
|
sub_role = None
|
|
switch_id = None
|
|
switch_type = None
|
|
max_cores = None
|
|
deployment_id = None
|
|
macsec_profile = {}
|
|
device_metas = meta.find(str(QName(ns, "Devices")))
|
|
for device in device_metas.findall(str(QName(ns1, "DeviceMetadata"))):
|
|
if device.find(str(QName(ns1, "Name"))).text.lower() == hname.lower():
|
|
properties = device.find(str(QName(ns1, "Properties")))
|
|
for device_property in properties.findall(str(QName(ns1, "DeviceProperty"))):
|
|
name = device_property.find(str(QName(ns1, "Name"))).text
|
|
value = device_property.find(str(QName(ns1, "Value"))).text
|
|
if name == "SubRole":
|
|
sub_role = value
|
|
elif name == "SwitchId":
|
|
switch_id = value
|
|
elif name == "SwitchType":
|
|
switch_type = value
|
|
elif name == "MaxCores":
|
|
max_cores = value
|
|
elif name == "DeploymentId":
|
|
deployment_id = value
|
|
elif name == 'MacSecProfile':
|
|
macsec_profile = parse_macsec_profile(value)
|
|
|
|
return sub_role, switch_id, switch_type, max_cores, deployment_id, macsec_profile
|
|
|
|
def parse_deviceinfo(meta, hwsku):
|
|
port_speeds = {}
|
|
port_descriptions = {}
|
|
sys_ports = {}
|
|
for device_info in meta.findall(str(QName(ns, "DeviceInfo"))):
|
|
dev_sku = device_info.find(str(QName(ns, "HwSku"))).text
|
|
if dev_sku == hwsku:
|
|
interfaces = device_info.find(str(QName(ns, "EthernetInterfaces"))).findall(str(QName(ns1, "EthernetInterface")))
|
|
interfaces = interfaces + device_info.find(str(QName(ns, "ManagementInterfaces"))).findall(str(QName(ns1, "ManagementInterface")))
|
|
for interface in interfaces:
|
|
alias = interface.find(str(QName(ns, "InterfaceName"))).text
|
|
speed = interface.find(str(QName(ns, "Speed"))).text
|
|
desc = interface.find(str(QName(ns, "Description")))
|
|
if desc != None:
|
|
port_descriptions[port_alias_map.get(alias, alias)] = desc.text
|
|
port_speeds[port_alias_map.get(alias, alias)] = speed
|
|
|
|
sysports = device_info.find(str(QName(ns, "SystemPorts")))
|
|
if sysports is not None:
|
|
for sysport in sysports.findall(str(QName(ns, "SystemPort"))):
|
|
portname = sysport.find(str(QName(ns, "Name"))).text
|
|
hostname = sysport.find(str(QName(ns, "Hostname")))
|
|
asic_name = sysport.find(str(QName(ns, "AsicName")))
|
|
system_port_id = sysport.find(str(QName(ns, "SystemPortId"))).text
|
|
switch_id = sysport.find(str(QName(ns, "SwitchId"))).text
|
|
core_id = sysport.find(str(QName(ns, "CoreId"))).text
|
|
core_port_id = sysport.find(str(QName(ns, "CorePortId"))).text
|
|
speed = sysport.find(str(QName(ns, "Speed"))).text
|
|
num_voq = sysport.find(str(QName(ns, "NumVoq"))).text
|
|
key = portname
|
|
if asic_name is not None:
|
|
key = "%s|%s" % (asic_name.text, key)
|
|
if hostname is not None:
|
|
key = "%s|%s" % (hostname.text, key)
|
|
sys_ports[key] = {"system_port_id": system_port_id, "switch_id": switch_id, "core_index": core_id, "core_port_index": core_port_id, "speed": speed, "num_voq": num_voq}
|
|
|
|
return port_speeds, port_descriptions, sys_ports
|
|
|
|
# Function to check if IP address is present in the key.
|
|
# If it is present, then the key would be a tuple.
|
|
def is_ip_prefix_in_key(key):
|
|
return (isinstance(key, tuple))
|
|
|
|
# Special parsing for spine chassis frontend
|
|
def parse_spine_chassis_fe(results, vni, lo_intfs, phyport_intfs, pc_intfs, pc_members, devices):
|
|
chassis_vnet ='VnetFE'
|
|
chassis_vxlan_tunnel = 'TunnelInt'
|
|
chassis_vni = vni
|
|
|
|
# Vxlan tunnel information
|
|
lo_addr = '0.0.0.0'
|
|
for lo in lo_intfs:
|
|
lo_network = ipaddress.ip_network(UNICODE_TYPE(lo[1]), False)
|
|
if lo_network.version == 4:
|
|
lo_addr = str(lo_network.network_address)
|
|
break
|
|
results['VXLAN_TUNNEL'] = {chassis_vxlan_tunnel: {
|
|
'src_ip': lo_addr
|
|
}}
|
|
|
|
# Vnet information
|
|
results['VNET'] = {chassis_vnet: {
|
|
'vxlan_tunnel': chassis_vxlan_tunnel,
|
|
'vni': chassis_vni
|
|
}}
|
|
|
|
# For each IP interface
|
|
for intf in phyport_intfs:
|
|
# A IP interface may have multiple entries.
|
|
# For example, "Ethernet0": {}", "Ethernet0|192.168.1.1": {}"
|
|
# We only care about the one without IP information
|
|
if is_ip_prefix_in_key(intf) == True:
|
|
continue
|
|
|
|
neighbor_router = results['DEVICE_NEIGHBOR'][intf]['name']
|
|
|
|
# If the neighbor router is an external router
|
|
if devices[neighbor_router]['type'] != chassis_backend_role:
|
|
# Enslave the interface to a Vnet
|
|
phyport_intfs[intf] = {'vnet_name': chassis_vnet}
|
|
|
|
# For each port channel IP interface
|
|
for pc_intf in pc_intfs:
|
|
# A port channel IP interface may have multiple entries.
|
|
# For example, "Portchannel0": {}", "Portchannel0|192.168.1.1": {}"
|
|
# We only care about the one without IP information
|
|
if is_ip_prefix_in_key(pc_intf) == True:
|
|
continue
|
|
|
|
intf_name = None
|
|
# Get a physical interface that belongs to this port channel
|
|
for pc_member in pc_members:
|
|
if pc_member[0] == pc_intf:
|
|
intf_name = pc_member[1]
|
|
break
|
|
|
|
if intf_name is None:
|
|
print('Warning: cannot find any interfaces that belong to %s' % (pc_intf), file=sys.stderr)
|
|
continue
|
|
|
|
# Get the neighbor router of this port channel interface
|
|
neighbor_router = results['DEVICE_NEIGHBOR'][intf_name]['name']
|
|
|
|
# If the neighbor router is an external router
|
|
if devices[neighbor_router]['type'] != chassis_backend_role:
|
|
# Enslave the port channel interface to a Vnet
|
|
pc_intfs[pc_intf] = {'vnet_name': chassis_vnet}
|
|
|
|
def parse_default_vxlan_decap(results, vni, lo_intfs):
|
|
vnet ='Vnet-default'
|
|
vxlan_tunnel = 'tunnel_v4'
|
|
|
|
# Vxlan tunnel information
|
|
lo_addr = '0.0.0.0'
|
|
for lo in lo_intfs:
|
|
lo_network = ipaddress.ip_network(UNICODE_TYPE(lo[1]), False)
|
|
if lo_network.version == 4:
|
|
lo_addr = str(lo_network.network_address)
|
|
break
|
|
results['VXLAN_TUNNEL'] = {vxlan_tunnel: {
|
|
'src_ip': lo_addr
|
|
}}
|
|
|
|
# Vnet information
|
|
results['VNET'] = {vnet: {
|
|
'vxlan_tunnel': vxlan_tunnel,
|
|
'scope': "default",
|
|
'vni': vni
|
|
}}
|
|
|
|
###############################################################################
|
|
#
|
|
# Post-processing functions
|
|
#
|
|
###############################################################################
|
|
|
|
def filter_acl_table_for_backend(acls, vlan_members):
|
|
filter_acls = {}
|
|
for acl_name, value in acls.items():
|
|
if 'everflow' not in acl_name.lower():
|
|
filter_acls[acl_name] = value
|
|
|
|
ports = set()
|
|
for vlan, member in vlan_members:
|
|
ports.add(member)
|
|
filter_acls['DATAACL'] = { 'policy_desc': 'DATAACL',
|
|
'stage': 'ingress',
|
|
'type': 'L3',
|
|
'ports': list(ports)
|
|
}
|
|
return filter_acls
|
|
|
|
def filter_acl_table_bindings(acls, neighbors, port_channels, pc_members, sub_role, device_type, is_storage_device, vlan_members):
|
|
if device_type == 'BackEndToRRouter' and is_storage_device:
|
|
return filter_acl_table_for_backend(acls, vlan_members)
|
|
|
|
filter_acls = {}
|
|
|
|
# If the asic role is BackEnd no ACL Table (Ctrl/Data/Everflow) is binded.
|
|
# This will be applicable in Multi-NPU Platforms.
|
|
|
|
if sub_role == BACKEND_ASIC_SUB_ROLE:
|
|
return filter_acls
|
|
|
|
front_port_channel_intf = []
|
|
|
|
# List of Backplane ports
|
|
backplane_port_list = [v for k,v in port_alias_map.items() if v.startswith(backplane_prefix())]
|
|
|
|
# Get the front panel port channel.
|
|
for port_channel_intf in port_channels:
|
|
backend_port_channel = any(lag_member[1] in backplane_port_list \
|
|
for lag_member in list(pc_members.keys()) if lag_member[0] == port_channel_intf)
|
|
if not backend_port_channel:
|
|
front_port_channel_intf.append(port_channel_intf)
|
|
|
|
for acl_table, group_params in acls.items():
|
|
group_type = group_params.get('type', None)
|
|
filter_acls[acl_table] = acls[acl_table]
|
|
|
|
# For Control Plane and Data ACL no filtering is needed
|
|
# Control Plane ACL has no Interface associated and
|
|
# Data Plane ACL Interface are attached via minigraph
|
|
# AclInterface.
|
|
if group_type != 'MIRROR' and group_type != 'MIRRORV6' and group_type != 'MIRROR_DSCP':
|
|
continue
|
|
|
|
# Filters out back-panel ports from the binding list for Everflow (Mirror)
|
|
# ACL tables. We define an "back-panel" port as one that is a member of a
|
|
# port channel connected to back asic or directly connected to back asic.
|
|
# This will be applicable in Multi-NPU Platforms.
|
|
front_panel_ports = []
|
|
for port in group_params.get('ports', []):
|
|
# Filter out backplane ports
|
|
if port in backplane_port_list:
|
|
continue
|
|
# Filter out backplane port channels
|
|
if port in port_channels and port not in front_port_channel_intf:
|
|
continue
|
|
front_panel_ports.append(port)
|
|
|
|
# Filters out inactive front-panel ports from the binding list for mirror
|
|
# ACL tables. We define an "active" port as one that is a member of a
|
|
# front pannel port channel or one that is connected to a neighboring device via front panel port.
|
|
active_ports = [port for port in front_panel_ports if port in neighbors.keys() or port in front_port_channel_intf]
|
|
|
|
if not active_ports:
|
|
print('Warning: mirror table {} in ACL_TABLE does not have any ports bound to it'.format(acl_table), file=sys.stderr)
|
|
|
|
filter_acls[acl_table]['ports'] = active_ports
|
|
|
|
return filter_acls
|
|
|
|
def enable_internal_bgp_session(bgp_sessions, filename, asic_name):
|
|
'''
|
|
In Multi-NPU session the internal sessions will always be up.
|
|
So adding the admin-status 'up' configuration to bgp sessions
|
|
BGP session between FrontEnd and BackEnd Asics are internal bgp sessions
|
|
'''
|
|
local_sub_role = parse_asic_sub_role(filename, asic_name)
|
|
|
|
for peer_ip in bgp_sessions.keys():
|
|
peer_name = bgp_sessions[peer_ip]['name']
|
|
peer_sub_role = parse_asic_sub_role(filename, peer_name)
|
|
if ((local_sub_role == FRONTEND_ASIC_SUB_ROLE and peer_sub_role == BACKEND_ASIC_SUB_ROLE) or
|
|
(local_sub_role == BACKEND_ASIC_SUB_ROLE and peer_sub_role == FRONTEND_ASIC_SUB_ROLE)):
|
|
bgp_sessions[peer_ip].update({'admin_status': 'up'})
|
|
|
|
def select_mmu_profiles(profile, platform, hwsku):
|
|
"""
|
|
Select MMU files based on the device metadata attribute - SonicQosProfile
|
|
if no QosProfile exists in the minigraph, then no action is needed.
|
|
if a profile exists in the minigraph,
|
|
- create a dir path to search 1 level down from the base path.
|
|
- if no such dir path exists, no action is needed.
|
|
- if a dir path exists, check for the presence of each file from
|
|
the copy list in the dir path and copy it over to the base path.
|
|
"""
|
|
if not profile:
|
|
return
|
|
|
|
files_to_copy = ['pg_profile_lookup.ini', 'qos.json.j2', 'buffers_defaults_t0.j2', 'buffers_defaults_t1.j2']
|
|
|
|
if os.environ.get("CFGGEN_UNIT_TESTING", "0") == "2":
|
|
for dir_path, dir_name, files in os.walk('/sonic/device'):
|
|
if platform in dir_path:
|
|
new_path = os.path.split(dir_path)[0]
|
|
break
|
|
else:
|
|
new_path = '/usr/share/sonic/device'
|
|
path = os.path.join(new_path, platform, hwsku)
|
|
|
|
dir_path = os.path.join(path, profile)
|
|
if os.path.exists(dir_path):
|
|
for file_item in files_to_copy:
|
|
file_in_dir = os.path.join(dir_path, file_item)
|
|
if os.path.isfile(file_in_dir):
|
|
base_file = os.path.join(path, file_item)
|
|
exec_cmd(["sudo", "cp", file_in_dir, base_file])
|
|
|
|
###############################################################################
|
|
#
|
|
# Main functions
|
|
#
|
|
###############################################################################
|
|
def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hwsku_config_file=None, fabric_port_config_file=None ):
|
|
""" Parse minigraph xml file.
|
|
|
|
Keyword arguments:
|
|
filename -- minigraph file name
|
|
platform -- device platform
|
|
port_config_file -- port config file name
|
|
asic_name -- asic name; to parse multi-asic device minigraph to
|
|
generate asic specific configuration.
|
|
fabric_port_config_file -- fabric port config file name
|
|
"""
|
|
|
|
root = ET.parse(filename).getroot()
|
|
|
|
u_neighbors = None
|
|
u_devices = None
|
|
acls = {}
|
|
acl_table_types = {}
|
|
hwsku = None
|
|
bgp_sessions = None
|
|
bgp_monitors = []
|
|
bgp_asn = None
|
|
intfs = None
|
|
dpg_ecmp_content = {}
|
|
png_ecmp_content = {}
|
|
vlan_intfs = None
|
|
pc_intfs = None
|
|
tunnel_intfs = None
|
|
tunnel_intfs_qos_remap_config = None
|
|
vlans = None
|
|
vlan_members = None
|
|
dhcp_relay_table = None
|
|
pcs = None
|
|
mgmt_intf = None
|
|
voq_inband_intfs = None
|
|
lo_intfs = None
|
|
neighbors = None
|
|
devices = None
|
|
sub_role = None
|
|
resource_type = None
|
|
downstream_subrole = None
|
|
docker_routing_config_mode = "separated"
|
|
port_speeds_default = {}
|
|
port_speed_png = {}
|
|
port_descriptions = {}
|
|
sys_ports = {}
|
|
console_ports = {}
|
|
mux_cable_ports = {}
|
|
syslog_servers = []
|
|
dhcp_servers = []
|
|
dhcpv6_servers = []
|
|
ntp_servers = []
|
|
tacacs_servers = []
|
|
mgmt_routes = []
|
|
erspan_dst = []
|
|
bgp_peers_with_range = None
|
|
deployment_id = None
|
|
region = None
|
|
cloudtype = None
|
|
switch_id = None
|
|
switch_type = None
|
|
max_cores = None
|
|
hostname = None
|
|
linkmetas = {}
|
|
host_lo_intfs = None
|
|
is_storage_device = False
|
|
local_devices = []
|
|
kube_data = {}
|
|
static_routes = {}
|
|
system_defaults = {}
|
|
macsec_profile = {}
|
|
downstream_redundancy_types = None
|
|
redundancy_type = None
|
|
qos_profile = None
|
|
rack_mgmt_map = None
|
|
|
|
hwsku_qn = QName(ns, "HwSku")
|
|
hostname_qn = QName(ns, "Hostname")
|
|
docker_routing_config_mode_qn = QName(ns, "DockerRoutingConfigMode")
|
|
for child in root:
|
|
if child.tag == str(hwsku_qn):
|
|
hwsku = child.text
|
|
if child.tag == str(hostname_qn):
|
|
hostname = child.text
|
|
if child.tag == str(docker_routing_config_mode_qn):
|
|
docker_routing_config_mode = child.text
|
|
|
|
(ports, alias_map, alias_asic_map) = get_port_config(hwsku=hwsku, platform=platform, port_config_file=port_config_file, asic_name=asic_name, hwsku_config_file=hwsku_config_file)
|
|
|
|
port_names_map.update(ports)
|
|
port_alias_map.update(alias_map)
|
|
port_alias_asic_map.update(alias_asic_map)
|
|
|
|
# Get the local device node from DeviceMetadata
|
|
local_devices = parse_asic_meta_get_devices(root)
|
|
|
|
for child in root:
|
|
if asic_name is None:
|
|
if child.tag == str(QName(ns, "DpgDec")):
|
|
(intfs, lo_intfs, mvrf, mgmt_intf, voq_inband_intfs, vlans, vlan_members, dhcp_relay_table, pcs, pc_members, acls, acl_table_types, vni, tunnel_intfs, dpg_ecmp_content, static_routes, tunnel_intfs_qos_remap_config) = parse_dpg(child, hostname)
|
|
elif child.tag == str(QName(ns, "CpgDec")):
|
|
(bgp_sessions, bgp_internal_sessions, bgp_voq_chassis_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, 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, None)
|
|
elif child.tag == str(QName(ns, "MetadataDeclaration")):
|
|
(syslog_servers, dhcp_servers, dhcpv6_servers, ntp_servers, tacacs_servers, mgmt_routes, erspan_dst, deployment_id, region, cloudtype, resource_type, downstream_subrole, switch_id, switch_type, max_cores, kube_data, macsec_profile, downstream_redundancy_types, redundancy_type, qos_profile, rack_mgmt_map) = parse_meta(child, hostname)
|
|
elif child.tag == str(QName(ns, "LinkMetadataDeclaration")):
|
|
linkmetas = parse_linkmeta(child, hostname)
|
|
elif child.tag == str(QName(ns, "DeviceInfos")):
|
|
(port_speeds_default, port_descriptions, sys_ports) = parse_deviceinfo(child, hwsku)
|
|
else:
|
|
if child.tag == str(QName(ns, "DpgDec")):
|
|
(intfs, lo_intfs, mvrf, mgmt_intf, voq_inband_intfs, vlans, vlan_members, dhcp_relay_table, pcs, pc_members, acls, acl_table_types, vni, tunnel_intfs, dpg_ecmp_content, static_routes, tunnel_intfs_qos_remap_config) = 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_voq_chassis_sessions, bgp_asn, bgp_peers_with_range, bgp_monitors) = parse_cpg(child, asic_name, local_devices)
|
|
elif child.tag == str(QName(ns, "PngDec")):
|
|
(neighbors, devices, port_speed_png) = parse_asic_png(child, asic_name, hostname)
|
|
elif child.tag == str(QName(ns, "MetadataDeclaration")):
|
|
(sub_role, switch_id, switch_type, max_cores, deployment_id, macsec_profile) = parse_asic_meta(child, asic_name)
|
|
elif child.tag == str(QName(ns, "LinkMetadataDeclaration")):
|
|
linkmetas = parse_linkmeta(child, hostname)
|
|
elif child.tag == str(QName(ns, "DeviceInfos")):
|
|
(port_speeds_default, port_descriptions, sys_ports) = parse_deviceinfo(child, hwsku)
|
|
|
|
select_mmu_profiles(qos_profile, platform, hwsku)
|
|
# set the host device type in asic metadata also
|
|
device_type = [devices[key]['type'] for key in devices if key.lower() == hostname.lower()][0]
|
|
if asic_name is None:
|
|
current_device = [devices[key] for key in devices if key.lower() == hostname.lower()][0]
|
|
else:
|
|
try:
|
|
current_device = [devices[key] for key in devices if key.lower() == asic_name.lower()][0]
|
|
except:
|
|
print("Warning: no asic configuration found for {} in minigraph".format(asic_name), file=sys.stderr)
|
|
current_device = {}
|
|
|
|
results = {}
|
|
results['DEVICE_METADATA'] = {'localhost': {
|
|
'bgp_asn': bgp_asn,
|
|
'region': region,
|
|
'cloudtype': cloudtype,
|
|
'docker_routing_config_mode': docker_routing_config_mode,
|
|
'hostname': hostname,
|
|
'hwsku': hwsku,
|
|
'type': device_type,
|
|
'synchronous_mode': 'enable',
|
|
'yang_config_validation': 'disable'
|
|
}
|
|
}
|
|
|
|
if deployment_id is not None:
|
|
results['DEVICE_METADATA']['localhost']['deployment_id'] = deployment_id
|
|
|
|
if rack_mgmt_map is not None:
|
|
results['DEVICE_METADATA']['localhost']['rack_mgmt_map'] = rack_mgmt_map
|
|
|
|
cluster = [devices[key] for key in devices if key.lower() == hostname.lower()][0].get('cluster', "")
|
|
if cluster:
|
|
results['DEVICE_METADATA']['localhost']['cluster'] = cluster
|
|
|
|
if kube_data:
|
|
results['KUBERNETES_MASTER'] = {
|
|
'SERVER': {
|
|
'disable': str(kube_data.get('enable', '0') == '0'),
|
|
'ip': kube_data.get('ip', '')
|
|
}
|
|
}
|
|
|
|
results['PEER_SWITCH'], mux_tunnel_name, peer_switch_ip = get_peer_switch_info(linkmetas, devices)
|
|
|
|
if bool(results['PEER_SWITCH']):
|
|
results['DEVICE_METADATA']['localhost']['subtype'] = 'DualToR'
|
|
if len(results['PEER_SWITCH'].keys()) > 1:
|
|
print("Warning: more than one peer switch was found. Only the first will be parsed: {}".format(results['PEER_SWITCH'].keys()[0]))
|
|
|
|
results['DEVICE_METADATA']['localhost']['peer_switch'] = list(results['PEER_SWITCH'].keys())[0]
|
|
|
|
# Enable tunnel_qos_remap if downstream_redundancy_types(T1) or redundancy_type(T0) = Gemini/Libra
|
|
enable_tunnel_qos_map = False
|
|
if platform and 'kvm' in platform:
|
|
enable_tunnel_qos_map = False
|
|
elif results['DEVICE_METADATA']['localhost']['type'].lower() == 'leafrouter' and ('gemini' in str(downstream_redundancy_types).lower() or 'libra' in str(downstream_redundancy_types).lower()):
|
|
enable_tunnel_qos_map = True
|
|
elif results['DEVICE_METADATA']['localhost']['type'].lower() == 'torrouter' and ('gemini' in str(redundancy_type).lower() or 'libra' in str(redundancy_type).lower()):
|
|
enable_tunnel_qos_map = True
|
|
|
|
if enable_tunnel_qos_map:
|
|
system_defaults['tunnel_qos_remap'] = {"status": "enabled"}
|
|
|
|
if len(system_defaults) > 0:
|
|
results['SYSTEM_DEFAULTS'] = system_defaults
|
|
|
|
# for this hostname, if sub_role is defined, add sub_role in
|
|
# device_metadata
|
|
if sub_role is not None:
|
|
current_device['sub_role'] = sub_role
|
|
results['DEVICE_METADATA']['localhost']['sub_role'] = sub_role
|
|
results['DEVICE_METADATA']['localhost']['asic_name'] = asic_name
|
|
elif switch_type == "voq":
|
|
# On Voq switches asic_name is mandatory even on single-asic devices
|
|
results['DEVICE_METADATA']['localhost']['asic_name'] = 'Asic0'
|
|
|
|
# on Voq system each asic has a switch_id
|
|
if switch_id is not None:
|
|
results['DEVICE_METADATA']['localhost']['switch_id'] = switch_id
|
|
# on Voq system each asic has a switch_type
|
|
if switch_type is not None:
|
|
results['DEVICE_METADATA']['localhost']['switch_type'] = switch_type
|
|
# on Voq system each asic has a max_cores
|
|
if max_cores is not None:
|
|
results['DEVICE_METADATA']['localhost']['max_cores'] = max_cores
|
|
|
|
# Voq systems have an inband interface
|
|
if voq_inband_intfs is not None:
|
|
results['VOQ_INBAND_INTERFACE'] = {}
|
|
for key in voq_inband_intfs:
|
|
results['VOQ_INBAND_INTERFACE'][key] = voq_inband_intfs[key]
|
|
|
|
if resource_type is not None:
|
|
results['DEVICE_METADATA']['localhost']['resource_type'] = resource_type
|
|
if 'Appliance' in resource_type:
|
|
parse_default_vxlan_decap(results, vni_default, lo_intfs)
|
|
|
|
if downstream_subrole is not None:
|
|
results['DEVICE_METADATA']['localhost']['downstream_subrole'] = downstream_subrole
|
|
|
|
results['BGP_NEIGHBOR'] = bgp_sessions
|
|
results['BGP_MONITORS'] = bgp_monitors
|
|
results['BGP_PEER_RANGE'] = bgp_peers_with_range
|
|
results['BGP_INTERNAL_NEIGHBOR'] = bgp_internal_sessions
|
|
results['BGP_VOQ_CHASSIS_NEIGHBOR'] = bgp_voq_chassis_sessions
|
|
if mgmt_routes:
|
|
# TODO: differentiate v4 and v6
|
|
next(iter(mgmt_intf.values()))['forced_mgmt_routes'] = mgmt_routes
|
|
results['MGMT_PORT'] = {}
|
|
results['MGMT_INTERFACE'] = {}
|
|
mgmt_intf_count = 0
|
|
mgmt_alias_reverse_mapping = {}
|
|
sorted_keys = natsorted(mgmt_intf.keys(), alg=natsortns.IGNORECASE, key=lambda x : "|".join(x))
|
|
for key in sorted_keys:
|
|
alias = key[0]
|
|
if alias in mgmt_alias_reverse_mapping:
|
|
name = mgmt_alias_reverse_mapping[alias]
|
|
else:
|
|
name = 'eth' + str(mgmt_intf_count)
|
|
mgmt_intf_count += 1
|
|
mgmt_alias_reverse_mapping[alias] = name
|
|
results['MGMT_PORT'][name] = {'alias': alias, 'admin_status': 'up'}
|
|
if alias in port_speeds_default:
|
|
results['MGMT_PORT'][name]['speed'] = port_speeds_default[alias]
|
|
results['MGMT_INTERFACE'][(name, key[1])] = mgmt_intf[key]
|
|
results['LOOPBACK_INTERFACE'] = {}
|
|
for lo_intf in lo_intfs:
|
|
results['LOOPBACK_INTERFACE'][lo_intf] = lo_intfs[lo_intf]
|
|
results['LOOPBACK_INTERFACE'][lo_intf[0]] = {}
|
|
|
|
if host_lo_intfs is not None:
|
|
for host_lo_intf in host_lo_intfs:
|
|
results['LOOPBACK_INTERFACE'][host_lo_intf] = host_lo_intfs[host_lo_intf]
|
|
results['LOOPBACK_INTERFACE'][host_lo_intf[0]] = {}
|
|
|
|
results['MGMT_VRF_CONFIG'] = mvrf
|
|
|
|
phyport_intfs = {}
|
|
vlan_intfs = {}
|
|
pc_intfs = {}
|
|
vlan_invert_mapping = { v['alias']:k for k,v in vlans.items() if 'alias' in v }
|
|
vlan_sub_intfs = {}
|
|
|
|
for intf in intfs:
|
|
if intf[0][0:4] == 'Vlan':
|
|
vlan_intfs[intf] = {}
|
|
|
|
if bool(results['PEER_SWITCH']):
|
|
vlan_intfs[intf[0]] = {
|
|
'proxy_arp': 'enabled',
|
|
'grat_arp': 'enabled'
|
|
}
|
|
else:
|
|
vlan_intfs[intf[0]] = {}
|
|
elif intf[0] in vlan_invert_mapping:
|
|
vlan_intfs[(vlan_invert_mapping[intf[0]], intf[1])] = {}
|
|
|
|
if bool(results['PEER_SWITCH']):
|
|
vlan_intfs[vlan_invert_mapping[intf[0]]] = {
|
|
'proxy_arp': 'enabled',
|
|
'grat_arp': 'enabled'
|
|
}
|
|
else:
|
|
vlan_intfs[vlan_invert_mapping[intf[0]]] = {}
|
|
elif VLAN_SUB_INTERFACE_SEPARATOR in intf[0]:
|
|
vlan_sub_intfs[intf] = {}
|
|
vlan_sub_intfs[intf[0]] = {'admin_status': 'up'}
|
|
elif intf[0][0:11] == 'PortChannel':
|
|
pc_intfs[intf] = {}
|
|
pc_intfs[intf[0]] = {}
|
|
else:
|
|
phyport_intfs[intf] = {}
|
|
phyport_intfs[intf[0]] = {}
|
|
|
|
results['INTERFACE'] = phyport_intfs
|
|
results['VLAN_INTERFACE'] = vlan_intfs
|
|
|
|
if sys_ports:
|
|
results['SYSTEM_PORT'] = sys_ports
|
|
|
|
for port_name in port_speeds_default:
|
|
# ignore port not in port_config.ini
|
|
if port_name not in ports:
|
|
continue
|
|
|
|
ports.setdefault(port_name, {})['speed'] = port_speeds_default[port_name]
|
|
|
|
for port_name in port_speed_png:
|
|
# not consider port not in port_config.ini
|
|
# If no port_config_file is found ports is empty so ignore this error
|
|
if port_config_file is not None:
|
|
if port_name not in ports:
|
|
print("Warning: ignore interface '%s' as it is not in the port_config.ini" % port_name, file=sys.stderr)
|
|
continue
|
|
|
|
# skip management ports
|
|
if port_name in mgmt_alias_reverse_mapping.keys():
|
|
continue
|
|
|
|
port_default_speed = port_speeds_default.get(port_name, None)
|
|
port_png_speed = port_speed_png[port_name]
|
|
|
|
# set Port Speed before lane update
|
|
ports.setdefault(port_name, {})['speed'] = port_png_speed
|
|
|
|
# when the port speed is changes from 400g to 100g/40g
|
|
# update the port lanes, use the first 4 lanes of the 400G port to support 100G/40G port
|
|
if port_default_speed == '400000' and (port_png_speed == '100000' or port_png_speed == '40000'):
|
|
port_lanes = ports[port_name].get('lanes', '').split(',')
|
|
# check if the 400g port has only 8 lanes
|
|
if len(port_lanes) != 8:
|
|
continue
|
|
updated_lanes = ",".join(port_lanes[:4])
|
|
ports[port_name]['lanes'] = updated_lanes
|
|
|
|
|
|
for port_name, port in list(ports.items()):
|
|
# get port alias from port_config.ini
|
|
alias = port.get('alias', port_name)
|
|
# generate default 100G FEC only if FECDisabled is not true and 'fec' is not defined in port_config.ini
|
|
# Note: FECDisabled only be effective on 100G port right now
|
|
if linkmetas.get(alias, {}).get('FECDisabled', '').lower() == 'true':
|
|
port['fec'] = 'none'
|
|
elif not port.get('fec') and port.get('speed') == '100000':
|
|
port['fec'] = 'rs'
|
|
|
|
# If AutoNegotiation is available in the minigraph, we override any value we may have received from port_config.ini
|
|
autoneg = linkmetas.get(alias, {}).get('AutoNegotiation')
|
|
if autoneg:
|
|
port['autoneg'] = 'on' if autoneg.lower() == 'true' else 'off'
|
|
|
|
# If macsec is enabled on interface, and profile is valid, add the profile to port
|
|
macsec_enabled = linkmetas.get(alias, {}).get('MacSecEnabled')
|
|
if macsec_enabled and 'PrimaryKey' in macsec_profile:
|
|
port['macsec'] = macsec_profile['PrimaryKey']
|
|
|
|
tx_power = linkmetas.get(alias, {}).get('tx_power')
|
|
if tx_power:
|
|
port['tx_power'] = tx_power
|
|
|
|
laser_freq = linkmetas.get(alias, {}).get('laser_freq')
|
|
if laser_freq:
|
|
port['laser_freq'] = laser_freq
|
|
|
|
# set port description if parsed from deviceinfo
|
|
for port_name in port_descriptions:
|
|
# ignore port not in port_config.ini
|
|
if port_name not in ports:
|
|
continue
|
|
|
|
ports.setdefault(port_name, {})['description'] = port_descriptions[port_name]
|
|
|
|
for port_name, port in ports.items():
|
|
if not port.get('description'):
|
|
if port_name in neighbors:
|
|
# for the ports w/o description set it to neighbor name:port
|
|
port['description'] = "%s:%s" % (neighbors[port_name]['name'], neighbors[port_name]['port'])
|
|
else:
|
|
# for the ports w/o neighbor info, set it to port alias
|
|
port['description'] = port.get('alias', port_name)
|
|
|
|
# set default port MTU as 9100 and default TPID 0x8100
|
|
for port in ports.values():
|
|
port['mtu'] = '9100'
|
|
port['tpid'] = '0x8100'
|
|
|
|
# asymmetric PFC is disabled by default
|
|
for port in ports.values():
|
|
port['pfc_asym'] = 'off'
|
|
|
|
# set physical port default admin status up
|
|
for port in phyport_intfs:
|
|
if port[0] in ports:
|
|
ports.get(port[0])['admin_status'] = 'up'
|
|
|
|
if len(vlan_sub_intfs):
|
|
for subintf in vlan_sub_intfs:
|
|
if not isinstance(subintf, tuple):
|
|
parent_port = subintf.split(".")[0]
|
|
if parent_port in ports:
|
|
ports.get(parent_port)['admin_status'] = 'up'
|
|
|
|
for member in list(pc_members.keys()) + list(vlan_members.keys()):
|
|
port = ports.get(member[1])
|
|
if port:
|
|
port['admin_status'] = 'up'
|
|
|
|
for port in neighbors.keys():
|
|
if port in ports.keys():
|
|
# make all neighbors connected ports to 'admin_up'
|
|
ports[port]['admin_status'] = 'up'
|
|
|
|
# bring up the inband voq interfaces
|
|
for inband_port in voq_inband_intfs:
|
|
if inband_port in ports.keys():
|
|
ports[inband_port]['admin_status'] = 'up'
|
|
|
|
# bring up the recirc port for voq chassis, Set it as routed interface
|
|
for port, port_attributes in ports.items():
|
|
port_role = port_attributes.get('role', None)
|
|
if port_role == 'Rec':
|
|
ports[port]['admin_status'] = 'up'
|
|
|
|
#Add the Recirc ports to the INTERFACES table to make it routed intf
|
|
results['INTERFACE'].update({port : {}})
|
|
|
|
results['PORT'] = ports
|
|
results['CONSOLE_PORT'] = console_ports
|
|
|
|
# Get the global fabric monitoring data
|
|
fabric_monitor = get_fabric_monitor_config(hwsku=hwsku, asic_name=asic_name)
|
|
if bool( fabric_monitor ):
|
|
results[ 'FABRIC_MONITOR' ] = fabric_monitor
|
|
|
|
# parse fabric
|
|
fabric_ports = get_fabric_port_config(hwsku=hwsku, platform=platform, fabric_port_config_file=fabric_port_config_file, asic_name=asic_name, hwsku_config_file=hwsku_config_file)
|
|
if bool( fabric_ports ):
|
|
results['FABRIC_PORT'] = fabric_ports
|
|
|
|
if port_config_file:
|
|
port_set = set(ports.keys())
|
|
for (pc_name, pc_member) in list(pc_members.keys()):
|
|
# remove portchannels that contain ports not existing in port_config.ini
|
|
# when port_config.ini exists
|
|
if (pc_name, pc_member) in pc_members and pc_member not in port_set:
|
|
print("Warning: ignore '%s' as at least one of its member interfaces ('%s') is not in the port_config.ini" % (pc_name, pc_member), file=sys.stderr)
|
|
del pcs[pc_name]
|
|
pc_mbr_del_keys = [f for f in list(pc_members.keys()) if f[0] == pc_name]
|
|
for pc_mbr_del_key in pc_mbr_del_keys:
|
|
del pc_members[pc_mbr_del_key]
|
|
|
|
# set default port channel MTU as 9100 and admin status up and default TPID 0x8100
|
|
for pc in pcs.values():
|
|
pc['mtu'] = '9100'
|
|
pc['tpid'] = '0x8100'
|
|
pc['admin_status'] = 'up'
|
|
|
|
results['PORTCHANNEL'] = pcs
|
|
results['PORTCHANNEL_MEMBER'] = pc_members
|
|
|
|
for pc_intf in list(pc_intfs.keys()):
|
|
# remove portchannels not in PORTCHANNEL dictionary
|
|
if isinstance(pc_intf, tuple) and pc_intf[0] not in pcs:
|
|
print("Warning: ignore '%s' interface '%s' as '%s' is not in the valid PortChannel list" % (pc_intf[0], pc_intf[1], pc_intf[0]), file=sys.stderr)
|
|
del pc_intfs[pc_intf]
|
|
pc_intfs.pop(pc_intf[0], None)
|
|
|
|
results['PORTCHANNEL_INTERFACE'] = pc_intfs
|
|
|
|
# for storage backend subinterface info present in minigraph takes precedence over ResourceType
|
|
if current_device and current_device['type'] in backend_device_types and bool(vlan_sub_intfs):
|
|
del results['INTERFACE']
|
|
del results['PORTCHANNEL_INTERFACE']
|
|
is_storage_device = True
|
|
results['VLAN_SUB_INTERFACE'] = vlan_sub_intfs
|
|
# storage backend T0 have all vlan members tagged
|
|
for vlan in vlan_members:
|
|
vlan_members[vlan]["tagging_mode"] = "tagged"
|
|
elif current_device and current_device['type'] in backend_device_types and (resource_type is None or 'Storage' in resource_type):
|
|
del results['INTERFACE']
|
|
del results['PORTCHANNEL_INTERFACE']
|
|
is_storage_device = True
|
|
|
|
for intf in phyport_intfs.keys():
|
|
if isinstance(intf, tuple):
|
|
intf_info = list(intf)
|
|
intf_info[0] = intf_info[0] + VLAN_SUB_INTERFACE_SEPARATOR + VLAN_SUB_INTERFACE_VLAN_ID
|
|
sub_intf = tuple(intf_info)
|
|
vlan_sub_intfs[sub_intf] = {}
|
|
else:
|
|
sub_intf = intf + VLAN_SUB_INTERFACE_SEPARATOR + VLAN_SUB_INTERFACE_VLAN_ID
|
|
vlan_sub_intfs[sub_intf] = {"admin_status" : "up"}
|
|
|
|
for pc_intf in pc_intfs.keys():
|
|
if isinstance(pc_intf, tuple):
|
|
pc_intf_info = list(pc_intf)
|
|
pc_intf_info[0] = pc_intf_info[0] + VLAN_SUB_INTERFACE_SEPARATOR + VLAN_SUB_INTERFACE_VLAN_ID
|
|
sub_intf = tuple(pc_intf_info)
|
|
vlan_sub_intfs[sub_intf] = {}
|
|
else:
|
|
sub_intf = pc_intf + VLAN_SUB_INTERFACE_SEPARATOR + VLAN_SUB_INTERFACE_VLAN_ID
|
|
vlan_sub_intfs[sub_intf] = {"admin_status" : "up"}
|
|
results['VLAN_SUB_INTERFACE'] = vlan_sub_intfs
|
|
# storage backend T0 have all vlan members tagged
|
|
for vlan in vlan_members:
|
|
vlan_members[vlan]["tagging_mode"] = "tagged"
|
|
elif resource_type is not None and 'Storage' in resource_type:
|
|
is_storage_device = True
|
|
elif bool(vlan_sub_intfs):
|
|
results['VLAN_SUB_INTERFACE'] = vlan_sub_intfs
|
|
|
|
|
|
if is_storage_device:
|
|
results['DEVICE_METADATA']['localhost']['storage_device'] = "true"
|
|
|
|
# remove bgp monitor and slb peers for storage backend
|
|
if is_storage_device and 'BackEnd' in current_device['type']:
|
|
results['BGP_MONITORS'] = {}
|
|
results['BGP_PEER_RANGE'] = {}
|
|
|
|
results['VLAN'] = vlans
|
|
results['VLAN_MEMBER'] = vlan_members
|
|
|
|
# Add src_ip and qos remapping config into TUNNEL table if tunnel_qos_remap is enabled
|
|
results['TUNNEL'] = get_tunnel_entries(tunnel_intfs, tunnel_intfs_qos_remap_config, lo_intfs, system_defaults.get('tunnel_qos_remap', {}), mux_tunnel_name, peer_switch_ip)
|
|
|
|
active_active_ports = get_ports_in_active_active(root, devices, neighbors)
|
|
results['MUX_CABLE'] = get_mux_cable_entries(ports, mux_cable_ports, active_active_ports, neighbors, devices, redundancy_type)
|
|
|
|
# If connected to a smart cable, get the connection position
|
|
for port_name, port in results['PORT'].items():
|
|
if port_name in results['MUX_CABLE']:
|
|
port['mux_cable'] = "true"
|
|
|
|
if static_routes:
|
|
results['STATIC_ROUTE'] = static_routes
|
|
|
|
for nghbr in list(neighbors.keys()):
|
|
# remove port not in port_config.ini
|
|
if nghbr not in ports:
|
|
if port_config_file is not None:
|
|
print("Warning: ignore interface '%s' in DEVICE_NEIGHBOR as it is not in the port_config.ini" % nghbr, file=sys.stderr)
|
|
del neighbors[nghbr]
|
|
results['DEVICE_NEIGHBOR'] = neighbors
|
|
if asic_name is None:
|
|
results['DEVICE_NEIGHBOR_METADATA'] = { key:devices[key] for key in devices if key.lower() != hostname.lower() }
|
|
else:
|
|
results['DEVICE_NEIGHBOR_METADATA'] = { key:devices[key] for key in devices if key in {device['name'] for device in neighbors.values()} }
|
|
results['SYSLOG_SERVER'] = dict((item, {}) for item in syslog_servers)
|
|
results['DHCP_SERVER'] = dict((item, {}) for item in dhcp_servers)
|
|
results['DHCP_RELAY'] = dhcp_relay_table
|
|
results['NTP_SERVER'] = dict((item, {}) for item in ntp_servers)
|
|
results['TACPLUS_SERVER'] = dict((item, {'priority': '1', 'tcp_port': '49'}) for item in tacacs_servers)
|
|
if len(acl_table_types) > 0:
|
|
results['ACL_TABLE_TYPE'] = acl_table_types
|
|
results['ACL_TABLE'] = filter_acl_table_bindings(acls, neighbors, pcs, pc_members, sub_role, current_device['type'] if current_device else None, is_storage_device, vlan_members)
|
|
results['FEATURE'] = {
|
|
'telemetry': {
|
|
'state': 'enabled'
|
|
}
|
|
}
|
|
results['TELEMETRY'] = {
|
|
'gnmi': {
|
|
'client_auth': 'true',
|
|
'port': '50051',
|
|
'log_level': '2'
|
|
},
|
|
'certs': {
|
|
'server_crt': '/etc/sonic/telemetry/streamingtelemetryserver.cer',
|
|
'server_key': '/etc/sonic/telemetry/streamingtelemetryserver.key',
|
|
'ca_crt': '/etc/sonic/telemetry/dsmsroot.cer'
|
|
}
|
|
}
|
|
results['RESTAPI'] = {
|
|
'config': {
|
|
'client_auth': 'true',
|
|
'allow_insecure': 'false',
|
|
'log_level': 'info'
|
|
},
|
|
'certs': {
|
|
'server_crt': '/etc/sonic/credentials/restapiserver.crt',
|
|
'server_key': '/etc/sonic/credentials/restapiserver.key',
|
|
'ca_crt': '/etc/sonic/credentials/restapica.crt',
|
|
'client_crt_cname': 'client.restapi.sonic'
|
|
}
|
|
}
|
|
|
|
if len(png_ecmp_content):
|
|
results['FG_NHG_MEMBER'] = png_ecmp_content['FG_NHG_MEMBER']
|
|
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:
|
|
# lo_addr = '0.0.0.0'
|
|
# for lo in lo_intfs:
|
|
# lo_network = ipaddress.ip_network(UNICODE_TYPE(lo[1]), False)
|
|
# if lo_network.version == 4:
|
|
# lo_addr = str(lo_network.network_address)
|
|
# break
|
|
# count = 0
|
|
# for dst in erspan_dst:
|
|
# mirror_sessions['everflow{}'.format(count)] = {"dst_ip": dst, "src_ip": lo_addr}
|
|
# count += 1
|
|
# results['MIRROR_SESSION'] = mirror_sessions
|
|
|
|
# Special parsing for spine chassis frontend routers
|
|
if current_device and current_device['type'] == spine_chassis_frontend_role:
|
|
parse_spine_chassis_fe(results, vni, lo_intfs, phyport_intfs, pc_intfs, pc_members, devices)
|
|
|
|
# Enable console management feature for console swtich
|
|
results['CONSOLE_SWITCH'] = {
|
|
'console_mgmt' : {
|
|
'enabled' : 'yes' if current_device and current_device['type'] in console_device_types else 'no'
|
|
}
|
|
}
|
|
|
|
# Enable DHCP Server feature for specific device type
|
|
if current_device and current_device['type'] in dhcp_server_enabled_device_types:
|
|
results['DEVICE_METADATA']['localhost']['dhcp_server'] = 'enabled'
|
|
|
|
# Disable unsupported counters on management devices
|
|
if current_device and current_device['type'] in mgmt_device_types:
|
|
results["FLEX_COUNTER_TABLE"] = {counter: {"FLEX_COUNTER_STATUS": "disable"} for counter in mgmt_disabled_counters}
|
|
|
|
return results
|
|
|
|
def get_tunnel_entries(tunnel_intfs, tunnel_intfs_qos_remap_config, lo_intfs, tunnel_qos_remap, mux_tunnel_name, peer_switch_ip):
|
|
lo_addr = ''
|
|
# Use the first IPv4 loopback as the tunnel destination IP
|
|
for addr in lo_intfs.keys():
|
|
ip_addr = ipaddress.ip_network(UNICODE_TYPE(addr[1]))
|
|
if isinstance(ip_addr, ipaddress.IPv4Network):
|
|
lo_addr = str(ip_addr.network_address)
|
|
break
|
|
|
|
tunnels = {}
|
|
|
|
default_qos_map_for_mux_tunnel = {
|
|
"decap_dscp_to_tc_map": "AZURE_TUNNEL",
|
|
"decap_tc_to_pg_map": "AZURE_TUNNEL",
|
|
"encap_tc_to_dscp_map": "AZURE_TUNNEL",
|
|
"encap_tc_to_queue_map": "AZURE_TUNNEL"
|
|
}
|
|
|
|
for type, tunnel_dict in tunnel_intfs.items():
|
|
for tunnel_key, tunnel_attr in tunnel_dict.items():
|
|
tunnel_attr['dst_ip'] = lo_addr
|
|
|
|
if (tunnel_qos_remap.get('status') == 'enabled') and (mux_tunnel_name == tunnel_key) and (peer_switch_ip is not None):
|
|
tunnel_attr['src_ip'] = peer_switch_ip
|
|
# The DSCP mode must be pipe if remap is enabled
|
|
tunnel_attr['dscp_mode'] = "pipe"
|
|
if tunnel_key in tunnel_intfs_qos_remap_config[type]:
|
|
tunnel_attr.update(tunnel_intfs_qos_remap_config[type][tunnel_key].items())
|
|
# Use default value if qos remap attribute is missing
|
|
for k, v in default_qos_map_for_mux_tunnel.items():
|
|
if k not in tunnel_attr:
|
|
tunnel_attr[k] = v
|
|
|
|
tunnels[tunnel_key] = tunnel_attr
|
|
|
|
return tunnels
|
|
|
|
|
|
def get_ports_in_active_active(root, devices, neighbors):
|
|
"""Parse out ports in active-active cable type."""
|
|
servers = {hostname.lower(): device_data for hostname, device_data in devices.items() if device_data["type"] == "Server"}
|
|
ports_in_active_active = {}
|
|
dpg_section = root.find(str(QName(ns, "DpgDec")))
|
|
neighbor_to_port_mapping = {neighbor["name"].lower(): port for port, neighbor in neighbors.items()}
|
|
if dpg_section is not None:
|
|
for child in dpg_section:
|
|
hostname = child.find(str(QName(ns, "Hostname")))
|
|
if hostname is None:
|
|
continue
|
|
hostname = hostname.text.lower()
|
|
if hostname not in servers:
|
|
continue
|
|
lo_intfs = parse_loopback_intf(child)
|
|
soc_intfs = {}
|
|
for intfname, ipprefix in lo_intfs.keys():
|
|
intfname_lower = intfname.lower()
|
|
if hostname + "soc" == intfname_lower:
|
|
ipprefix = str(ipaddress.ip_network(UNICODE_TYPE(ipprefix.split("/")[0])))
|
|
if "." in ipprefix:
|
|
soc_intfs["soc_ipv4"] = ipprefix
|
|
elif ":" in ipprefix:
|
|
soc_intfs["soc_ipv6"] = ipprefix
|
|
if hostname in neighbor_to_port_mapping and soc_intfs:
|
|
ports_in_active_active[neighbor_to_port_mapping[hostname]] = soc_intfs
|
|
return ports_in_active_active
|
|
|
|
|
|
def get_mux_cable_entries(ports, mux_cable_ports, active_active_ports, neighbors, devices, redundancy_type):
|
|
mux_cable_table = {}
|
|
if redundancy_type:
|
|
redundancy_type = redundancy_type.lower()
|
|
|
|
for port in ports:
|
|
is_active_active = redundancy_type in ("libra", "mixed") and port in active_active_ports
|
|
is_active_standby = port in mux_cable_ports
|
|
if is_active_active and is_active_standby:
|
|
print("Warning: skip %s as it is defined as active-standby and actie-active" % port, file=sys.stderr)
|
|
continue
|
|
if not (is_active_active or is_active_standby):
|
|
continue
|
|
|
|
entry = {}
|
|
neighbor = neighbors[port]['name']
|
|
entry['state'] = 'auto'
|
|
|
|
if devices[neighbor]['lo_addr'] is not None:
|
|
# Always force a /32 prefix for server IPv4 loopbacks
|
|
server_ipv4_lo_addr = devices[neighbor]['lo_addr'].split("/")[0]
|
|
server_ipv4_lo_prefix = ipaddress.ip_network(UNICODE_TYPE(server_ipv4_lo_addr))
|
|
entry['server_ipv4'] = str(server_ipv4_lo_prefix)
|
|
|
|
if 'lo_addr_v6' in devices[neighbor] and devices[neighbor]['lo_addr_v6'] is not None:
|
|
server_ipv6_lo_addr = devices[neighbor]['lo_addr_v6'].split('/')[0]
|
|
server_ipv6_lo_prefix = ipaddress.ip_network(UNICODE_TYPE(server_ipv6_lo_addr))
|
|
entry['server_ipv6'] = str(server_ipv6_lo_prefix)
|
|
|
|
if is_active_active:
|
|
entry['cable_type'] = 'active-active'
|
|
entry.update(active_active_ports[port])
|
|
|
|
mux_cable_table[port] = entry
|
|
else:
|
|
print("Warning: no server IPv4 loopback found for {}, skipping mux cable table entry".format(neighbor), file=sys.stderr)
|
|
|
|
return mux_cable_table
|
|
|
|
|
|
def parse_device_desc_xml(filename):
|
|
root = ET.parse(filename).getroot()
|
|
(lo_prefix, lo_prefix_v6, mgmt_prefix, mgmt_prefix_v6, hostname, hwsku, d_type, _, _, _) = parse_device(root)
|
|
|
|
results = {}
|
|
results['DEVICE_METADATA'] = {'localhost': {
|
|
'hostname': hostname,
|
|
'hwsku': hwsku,
|
|
}}
|
|
|
|
results['LOOPBACK_INTERFACE'] = {('lo', lo_prefix): {}}
|
|
if lo_prefix_v6:
|
|
results['LOOPBACK_INTERFACE'] = {('lo_v6', lo_prefix_v6): {}}
|
|
|
|
results['MGMT_INTERFACE'] = {}
|
|
if mgmt_prefix:
|
|
mgmtipn = ipaddress.ip_network(UNICODE_TYPE(mgmt_prefix), False)
|
|
if mgmtipn != ipaddress.ip_network(u'0.0.0.0/0', False):
|
|
gwaddr = ipaddress.ip_address((next(mgmtipn.hosts())))
|
|
results['MGMT_INTERFACE'].update({('eth0', mgmt_prefix): {'gwaddr': gwaddr}})
|
|
|
|
if mgmt_prefix_v6:
|
|
mgmtipn_v6 = ipaddress.ip_network(UNICODE_TYPE(mgmt_prefix_v6), False)
|
|
if mgmtipn != ipaddress.ip_network(u'::/0', False):
|
|
gwaddr_v6 = ipaddress.ip_address((next(mgmtipn_v6.hosts())))
|
|
results['MGMT_INTERFACE'].update({('eth0', mgmt_prefix_v6): {'gwaddr': gwaddr_v6}})
|
|
|
|
return results
|
|
|
|
def parse_asic_sub_role(filename, asic_name):
|
|
if not os.path.isfile(filename):
|
|
return None
|
|
root = ET.parse(filename).getroot()
|
|
for child in root:
|
|
if child.tag == str(QName(ns, "MetadataDeclaration")):
|
|
sub_role, _, _, _, _, _= parse_asic_meta(child, asic_name)
|
|
return sub_role
|
|
|
|
def parse_asic_switch_type(filename, asic_name):
|
|
if os.path.isfile(filename):
|
|
root = ET.parse(filename).getroot()
|
|
for child in root:
|
|
if child.tag == str(QName(ns, "MetadataDeclaration")):
|
|
_, _, switch_type, _, _, _ = parse_asic_meta(child, asic_name)
|
|
return switch_type
|
|
return None
|
|
|
|
def parse_asic_meta_get_devices(root):
|
|
local_devices = []
|
|
|
|
for child in root:
|
|
if child.tag == str(QName(ns, "MetadataDeclaration")):
|
|
device_metas = child.find(str(QName(ns, "Devices")))
|
|
for device in device_metas.findall(str(QName(ns1, "DeviceMetadata"))):
|
|
name = device.find(str(QName(ns1, "Name"))).text.lower()
|
|
local_devices.append(name)
|
|
|
|
return local_devices
|
|
|
|
port_names_map = {}
|
|
port_alias_map = {}
|
|
port_alias_asic_map = {}
|
|
|
|
|
|
def print_parse_xml(filename):
|
|
results = parse_xml(filename)
|
|
print((json.dumps(results, indent=3, cls=minigraph_encoder)))
|