def parse_dpg()

in src/sonic-config-engine/minigraph.py [0:0]


def parse_dpg(dpg, hname):
    aclintfs = None
    mgmtintfs = None
    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 aclintfs is None and child.find(str(QName(ns, "AclInterfaces"))) is not None:
            aclintfs = child.find(str(QName(ns, "AclInterfaces")))
        """
            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 mgmtintfs is None and child.find(str(QName(ns, "ManagementIPInterfaces"))) is not None:
            mgmtintfs = child.find(str(QName(ns, "ManagementIPInterfaces")))
        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.findall(str(QName(ns1, "ManagementIPInterface"))):
            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.findall(str(QName(ns, "AclInterface"))):
            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