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'
}
}