diff --git a/src/sonic-config-engine/minigraph.py b/src/sonic-config-engine/minigraph.py index 2bcef2232aef..b79ffff039c6 100644 --- a/src/sonic-config-engine/minigraph.py +++ b/src/sonic-config-engine/minigraph.py @@ -185,6 +185,7 @@ def formulate_fine_grained_ecmp(version, dpg_ecmp_content, port_device_map, port 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 = {} @@ -400,9 +401,9 @@ def parse_asic_png(png, asic_name, hostname): device_data['lo_addr_v6']= lo_prefix_v6 devices[name] = device_data - return (neighbors, devices, port_speeds) + def parse_loopback_intf(child): lointfs = child.find(str(QName(ns, "LoopbackIPInterfaces"))) lo_intfs = {} @@ -412,6 +413,7 @@ def parse_loopback_intf(child): lo_intfs[(intfname, ipprefix)] = {} return lo_intfs + def parse_dpg(dpg, hname): aclintfs = None mgmtintfs = None @@ -455,7 +457,7 @@ def parse_dpg(dpg, hname): ipprefix = ipintf.find(str(QName(ns, "Prefix"))).text intfs[(intfname, ipprefix)] = {} ip_intfs_map[ipprefix] = intfalias - lo_intfs = parse_loopback_intf(child) + lo_intfs = parse_loopback_intf(child) subintfs = child.find(str(QName(ns, "SubInterfaces"))) if subintfs is not None: @@ -757,7 +759,6 @@ def parse_dpg(dpg, hname): return None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None - def parse_host_loopback(dpg, hname): for child in dpg: hostname = child.find(str(QName(ns, "Hostname"))) @@ -766,6 +767,7 @@ def parse_host_loopback(dpg, hname): lo_intfs = parse_loopback_intf(child) return lo_intfs + def parse_cpg(cpg, hname, local_devices=[]): bgp_sessions = {} bgp_internal_sessions = {} @@ -891,6 +893,7 @@ def parse_meta(meta, hname): max_cores = None kube_data = {} macsec_profile = {} + redundancy_type = 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(): @@ -933,7 +936,9 @@ def parse_meta(meta, hname): kube_data["ip"] = value elif name == 'MacSecProfile': macsec_profile = parse_macsec_profile(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 + elif name == "RedundancyType": + redundancy_type = 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, redundancy_type def parse_system_defaults(meta): @@ -1313,6 +1318,7 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw static_routes = {} system_defaults = {} macsec_profile = {} + redundancy_type = None hwsku_qn = QName(ns, "HwSku") hostname_qn = QName(ns, "Hostname") @@ -1343,7 +1349,7 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw 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) = parse_meta(child, hostname) + (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, redundancy_type) = parse_meta(child, hostname) elif child.tag == str(QName(ns, "LinkMetadataDeclaration")): linkmetas = parse_linkmeta(child, hostname) elif child.tag == str(QName(ns, "DeviceInfos")): @@ -1567,11 +1573,6 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw if macsec_enabled and 'PrimaryKey' in macsec_profile: port['macsec'] = macsec_profile['PrimaryKey'] - # If connected to a smart cable, get the connection position - for port_name, port in ports.items(): - if port_name in mux_cable_ports: - port['mux_cable'] = "true" - # set port description if parsed from deviceinfo for port_name in port_descriptions: # ignore port not in port_config.ini @@ -1713,7 +1714,13 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw # 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) - results['MUX_CABLE'] = get_mux_cable_entries(mux_cable_ports, neighbors, devices) + 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 @@ -1826,46 +1833,76 @@ def get_tunnel_entries(tunnel_intfs, tunnel_intfs_qos_remap_config, lo_intfs, tu return tunnels -def get_mux_cable_entries(mux_cable_ports, neighbors, devices): + +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 - for intf, cable_name in mux_cable_ports.items(): - if intf in neighbors: - entry = {} - neighbor = neighbors[intf]['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) - mux_cable_table[intf] = entry - else: - print("Warning: no server IPv4 loopback found for {}, skipping mux cable table entry".format(neighbor), file=sys.stderr) + entry = {} + neighbor = neighbors[port]['name'] + entry['state'] = 'auto' - if cable_name in devices: - cable_type = devices[cable_name].get('subtype') - if cable_type is None: - continue - if cable_type in dualtor_cable_types: - mux_cable_table[intf]['cable_type'] = cable_type - if cable_type == 'active-active': - soc_ipv4 = devices[cable_name]['lo_addr'].split('/')[0] - soc_ipv4_prefix = ipaddress.ip_network(UNICODE_TYPE(soc_ipv4)) - mux_cable_table[intf]['soc_ipv4'] = str(soc_ipv4_prefix) - else: - print("Warning: skip parsing device %s for mux cable entry, cable type %s not supported" % (cable_name, cable_type), file=sys.stderr) + 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: skip parsing device %s for mux cable entry, device definition not found" % cable_name, file=sys.stderr) + 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) diff --git a/src/sonic-config-engine/tests/simple-sample-graph-case.xml b/src/sonic-config-engine/tests/simple-sample-graph-case.xml index 7bbef28aaa38..4165647a9aa3 100644 --- a/src/sonic-config-engine/tests/simple-sample-graph-case.xml +++ b/src/sonic-config-engine/tests/simple-sample-graph-case.xml @@ -198,6 +198,62 @@ + + + + + LoopbackInterface + HostIP + Loopback0 + + 10.10.10.2/32 + + 10.10.10.2/32 + + + LoopbackInterface + HostIP1 + Loopback0 + + fe80::0002/128 + + fe80::0002/128 + + + LoopbackInterface + SoCHostIP0 + server2SOC + + 10.10.10.3/32 + + 10.10.10.3/32 + + + LoopbackInterface + SoCHostIP1 + server2SOC + + fe80::0003/128 + + fe80::0003/128 + + + + + + + + server2 + + + + + + + + + + @@ -262,17 +318,6 @@ L true - - LogicalLink - 10000 - false - switch-t0 - fortyGigE0/8 - true - server2-SC - U - true - LogicalLink 0 @@ -349,25 +394,6 @@ server1 server-sku - - SmartCable - active-active -
- 10.10.10.3/32 -
- - ::/0 - - - 0.0.0.0/0 - - - ::/0 - - - server2-SC - smartcable-sku -
Server
@@ -506,6 +532,11 @@ Storage + + RedundancyType + + Mixed + diff --git a/src/sonic-config-engine/tests/test_minigraph_case.py b/src/sonic-config-engine/tests/test_minigraph_case.py index 16ad019032f2..bfee76c7546e 100644 --- a/src/sonic-config-engine/tests/test_minigraph_case.py +++ b/src/sonic-config-engine/tests/test_minigraph_case.py @@ -236,14 +236,6 @@ def test_minigraph_neighbor_metadata(self): 'lo_addr_v6': '::/0', 'mgmt_addr': '0.0.0.0/0', 'type': 'SmartCable' - }, - 'server2-SC': { - 'hwsku': 'smartcable-sku', - 'lo_addr': '10.10.10.3/32', - 'lo_addr_v6': '::/0', - 'mgmt_addr': '0.0.0.0/0', - 'type': 'SmartCable', - 'subtype': 'active-active' } } output = self.run_script(argument) @@ -421,6 +413,7 @@ def test_minigraph_mux_cable_table(self): 'server_ipv4': '10.10.10.2/32', 'server_ipv6': 'fe80::2/128', 'soc_ipv4': '10.10.10.3/32', + 'soc_ipv6': 'fe80::3/128', 'cable_type': 'active-active' } }