[minigraph][dualtor] Support parsing soc_ip out of dpg (#11207)

Why I did it
To further support parse out soc_ipv4 and soc_ipv6 out of Dpg:

<DeviceDataPlaneInfo>
  <IPSecTunnels />
  <LoopbackIPInterfaces xmlns:a="http://schemas.datacontract.org/2004/07/Microsoft.Search.Autopilot.Evolution">
    <a:LoopbackIPInterface>
      <ElementType>LoopbackInterface</ElementType>
      <Name>HostIP</Name>
      <AttachTo>Loopback0</AttachTo>
      <a:Prefix xmlns:b="Microsoft.Search.Autopilot.NetMux">
        <b:IPPrefix>10.10.10.2/32</b:IPPrefix>
      </a:Prefix>
      <a:PrefixStr>10.10.10.2/32</a:PrefixStr>
    </a:LoopbackIPInterface>
    <a:LoopbackIPInterface>
      <ElementType>LoopbackInterface</ElementType>
      <Name>HostIP1</Name>
      <AttachTo>Loopback0</AttachTo>
      <a:Prefix xmlns:b="Microsoft.Search.Autopilot.NetMux">
        <b:IPPrefix>fe80::0002/128</b:IPPrefix>
      </a:Prefix>
      <a:PrefixStr>fe80::0002/128</a:PrefixStr>
    </a:LoopbackIPInterface>
    <a:LoopbackIPInterface>
      <ElementType>LoopbackInterface</ElementType>
      <Name>SoCHostIP0</Name>
      <AttachTo>server2SOC</AttachTo>
      <a:Prefix xmlns:b="Microsoft.Search.Autopilot.NetMux">
        <b:IPPrefix>10.10.10.3/32</b:IPPrefix>
      </a:Prefix>
      <a:PrefixStr>10.10.10.3/32</a:PrefixStr>
    </a:LoopbackIPInterface>
    <a:LoopbackIPInterface>
      <ElementType>LoopbackInterface</ElementType>
      <Name>SoCHostIP1</Name>
      <AttachTo>server2SOC</AttachTo>
      <a:Prefix xmlns:b="Microsoft.Search.Autopilot.NetMux">
        <b:IPPrefix>fe80::0003/128</b:IPPrefix>
      </a:Prefix>
      <a:PrefixStr>fe80::0003/128</a:PrefixStr>
    </a:LoopbackIPInterface>
  </LoopbackIPInterfaces>
</DeviceDataPlaneInfo>
Signed-off-by: Longxiang Lyu lolv@microsoft.com

How I did it
For servers loopback definitions in Dpg, if they contain LoopbackIPInterface with tags AttachTo, which has value of format like <server_name>SOC, the address will be regarded as a SoC IP, and sonic-cfggen now will treat the port connected to the server as active-active if the redundancy_type is either Libra or Mixed.

How to verify it
Pass the unittest.

Signed-off-by: Longxiang Lyu <lolv@microsoft.com>
This commit is contained in:
Longxiang Lyu 2022-07-06 06:50:05 +08:00 committed by GitHub
parent ebe4a84eee
commit b9a7665662
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 144 additions and 83 deletions

View File

@ -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} fine_grained_content = {"FG_NHG_MEMBER": FG_NHG_MEMBER, "FG_NHG": FG_NHG, "NEIGH": NEIGH}
return fine_grained_content return fine_grained_content
def parse_png(png, hname, dpg_ecmp_content = None): def parse_png(png, hname, dpg_ecmp_content = None):
neighbors = {} neighbors = {}
devices = {} devices = {}
@ -400,9 +401,9 @@ def parse_asic_png(png, asic_name, hostname):
device_data['lo_addr_v6']= lo_prefix_v6 device_data['lo_addr_v6']= lo_prefix_v6
devices[name] = device_data devices[name] = device_data
return (neighbors, devices, port_speeds) return (neighbors, devices, port_speeds)
def parse_loopback_intf(child): def parse_loopback_intf(child):
lointfs = child.find(str(QName(ns, "LoopbackIPInterfaces"))) lointfs = child.find(str(QName(ns, "LoopbackIPInterfaces")))
lo_intfs = {} lo_intfs = {}
@ -412,6 +413,7 @@ def parse_loopback_intf(child):
lo_intfs[(intfname, ipprefix)] = {} lo_intfs[(intfname, ipprefix)] = {}
return lo_intfs return lo_intfs
def parse_dpg(dpg, hname): def parse_dpg(dpg, hname):
aclintfs = None aclintfs = None
mgmtintfs = None mgmtintfs = None
@ -455,7 +457,7 @@ def parse_dpg(dpg, hname):
ipprefix = ipintf.find(str(QName(ns, "Prefix"))).text ipprefix = ipintf.find(str(QName(ns, "Prefix"))).text
intfs[(intfname, ipprefix)] = {} intfs[(intfname, ipprefix)] = {}
ip_intfs_map[ipprefix] = intfalias ip_intfs_map[ipprefix] = intfalias
lo_intfs = parse_loopback_intf(child) lo_intfs = parse_loopback_intf(child)
subintfs = child.find(str(QName(ns, "SubInterfaces"))) subintfs = child.find(str(QName(ns, "SubInterfaces")))
if subintfs is not None: 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 return None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None
def parse_host_loopback(dpg, hname): def parse_host_loopback(dpg, hname):
for child in dpg: for child in dpg:
hostname = child.find(str(QName(ns, "Hostname"))) hostname = child.find(str(QName(ns, "Hostname")))
@ -766,6 +767,7 @@ def parse_host_loopback(dpg, hname):
lo_intfs = parse_loopback_intf(child) lo_intfs = parse_loopback_intf(child)
return lo_intfs return lo_intfs
def parse_cpg(cpg, hname, local_devices=[]): def parse_cpg(cpg, hname, local_devices=[]):
bgp_sessions = {} bgp_sessions = {}
bgp_internal_sessions = {} bgp_internal_sessions = {}
@ -891,6 +893,7 @@ def parse_meta(meta, hname):
max_cores = None max_cores = None
kube_data = {} kube_data = {}
macsec_profile = {} macsec_profile = {}
redundancy_type = None
device_metas = meta.find(str(QName(ns, "Devices"))) device_metas = meta.find(str(QName(ns, "Devices")))
for device in device_metas.findall(str(QName(ns1, "DeviceMetadata"))): for device in device_metas.findall(str(QName(ns1, "DeviceMetadata"))):
if device.find(str(QName(ns1, "Name"))).text.lower() == hname.lower(): if device.find(str(QName(ns1, "Name"))).text.lower() == hname.lower():
@ -933,7 +936,9 @@ def parse_meta(meta, hname):
kube_data["ip"] = value kube_data["ip"] = value
elif name == 'MacSecProfile': elif name == 'MacSecProfile':
macsec_profile = parse_macsec_profile(value) 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): 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 = {} static_routes = {}
system_defaults = {} system_defaults = {}
macsec_profile = {} macsec_profile = {}
redundancy_type = None
hwsku_qn = QName(ns, "HwSku") hwsku_qn = QName(ns, "HwSku")
hostname_qn = QName(ns, "Hostname") 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")): elif child.tag == str(QName(ns, "UngDec")):
(u_neighbors, u_devices, _, _, _, _, _, _) = parse_png(child, hostname, None) (u_neighbors, u_devices, _, _, _, _, _, _) = parse_png(child, hostname, None)
elif child.tag == str(QName(ns, "MetadataDeclaration")): 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")): elif child.tag == str(QName(ns, "LinkMetadataDeclaration")):
linkmetas = parse_linkmeta(child, hostname) linkmetas = parse_linkmeta(child, hostname)
elif child.tag == str(QName(ns, "DeviceInfos")): 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: if macsec_enabled and 'PrimaryKey' in macsec_profile:
port['macsec'] = macsec_profile['PrimaryKey'] 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 # set port description if parsed from deviceinfo
for port_name in port_descriptions: for port_name in port_descriptions:
# ignore port not in port_config.ini # 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 # 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['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: if static_routes:
results['STATIC_ROUTE'] = 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 return tunnels
def get_mux_cable_entries(mux_cable_ports, neighbors, devices):
mux_cable_table = {}
for intf, cable_name in mux_cable_ports.items(): def get_ports_in_active_active(root, devices, neighbors):
if intf in neighbors: """Parse out ports in active-active cable type."""
entry = {} servers = {hostname.lower(): device_data for hostname, device_data in devices.items() if device_data["type"] == "Server"}
neighbor = neighbors[intf]['name'] ports_in_active_active = {}
entry['state'] = 'auto' dpg_section = root.find(str(QName(ns, "DpgDec")))
neighbor_to_port_mapping = {neighbor["name"].lower(): port for port, neighbor in neighbors.items()}
if devices[neighbor]['lo_addr'] is not None: if dpg_section is not None:
# Always force a /32 prefix for server IPv4 loopbacks for child in dpg_section:
server_ipv4_lo_addr = devices[neighbor]['lo_addr'].split("/")[0] hostname = child.find(str(QName(ns, "Hostname")))
server_ipv4_lo_prefix = ipaddress.ip_network(UNICODE_TYPE(server_ipv4_lo_addr)) if hostname is None:
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)
if cable_name in devices:
cable_type = devices[cable_name].get('subtype')
if cable_type is None:
continue continue
if cable_type in dualtor_cable_types: hostname = hostname.text.lower()
mux_cable_table[intf]['cable_type'] = cable_type if hostname not in servers:
if cable_type == 'active-active': continue
soc_ipv4 = devices[cable_name]['lo_addr'].split('/')[0] lo_intfs = parse_loopback_intf(child)
soc_ipv4_prefix = ipaddress.ip_network(UNICODE_TYPE(soc_ipv4)) soc_intfs = {}
mux_cable_table[intf]['soc_ipv4'] = str(soc_ipv4_prefix) for intfname, ipprefix in lo_intfs.keys():
else: intfname_lower = intfname.lower()
print("Warning: skip parsing device %s for mux cable entry, cable type %s not supported" % (cable_name, cable_type), file=sys.stderr) 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: 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 return mux_cable_table
def parse_device_desc_xml(filename): def parse_device_desc_xml(filename):
root = ET.parse(filename).getroot() root = ET.parse(filename).getroot()
(lo_prefix, lo_prefix_v6, mgmt_prefix, mgmt_prefix_v6, hostname, hwsku, d_type, _, _, _) = parse_device(root) (lo_prefix, lo_prefix_v6, mgmt_prefix, mgmt_prefix_v6, hostname, hwsku, d_type, _, _, _) = parse_device(root)

View File

@ -198,6 +198,62 @@
<DownstreamSummaries/> <DownstreamSummaries/>
<DownstreamSummarySet xmlns:a="http://schemas.datacontract.org/2004/07/Microsoft.Search.Autopilot.Evolution"/> <DownstreamSummarySet xmlns:a="http://schemas.datacontract.org/2004/07/Microsoft.Search.Autopilot.Evolution"/>
</DeviceDataPlaneInfo> </DeviceDataPlaneInfo>
<DeviceDataPlaneInfo>
<IPSecTunnels />
<LoopbackIPInterfaces xmlns:a="http://schemas.datacontract.org/2004/07/Microsoft.Search.Autopilot.Evolution">
<a:LoopbackIPInterface>
<ElementType>LoopbackInterface</ElementType>
<Name>HostIP</Name>
<AttachTo>Loopback0</AttachTo>
<a:Prefix xmlns:b="Microsoft.Search.Autopilot.NetMux">
<b:IPPrefix>10.10.10.2/32</b:IPPrefix>
</a:Prefix>
<a:PrefixStr>10.10.10.2/32</a:PrefixStr>
</a:LoopbackIPInterface>
<a:LoopbackIPInterface>
<ElementType>LoopbackInterface</ElementType>
<Name>HostIP1</Name>
<AttachTo>Loopback0</AttachTo>
<a:Prefix xmlns:b="Microsoft.Search.Autopilot.NetMux">
<b:IPPrefix>fe80::0002/128</b:IPPrefix>
</a:Prefix>
<a:PrefixStr>fe80::0002/128</a:PrefixStr>
</a:LoopbackIPInterface>
<a:LoopbackIPInterface>
<ElementType>LoopbackInterface</ElementType>
<Name>SoCHostIP0</Name>
<AttachTo>server2SOC</AttachTo>
<a:Prefix xmlns:b="Microsoft.Search.Autopilot.NetMux">
<b:IPPrefix>10.10.10.3/32</b:IPPrefix>
</a:Prefix>
<a:PrefixStr>10.10.10.3/32</a:PrefixStr>
</a:LoopbackIPInterface>
<a:LoopbackIPInterface>
<ElementType>LoopbackInterface</ElementType>
<Name>SoCHostIP1</Name>
<AttachTo>server2SOC</AttachTo>
<a:Prefix xmlns:b="Microsoft.Search.Autopilot.NetMux">
<b:IPPrefix>fe80::0003/128</b:IPPrefix>
</a:Prefix>
<a:PrefixStr>fe80::0003/128</a:PrefixStr>
</a:LoopbackIPInterface>
</LoopbackIPInterfaces>
<ManagementIPInterfaces xmlns:a="http://schemas.datacontract.org/2004/07/Microsoft.Search.Autopilot.Evolution" />
<ManagementVIPInterfaces xmlns:a="http://schemas.datacontract.org/2004/07/Microsoft.Search.Autopilot.Evolution" />
<MplsInterfaces />
<MplsTeInterfaces />
<RsvpInterfaces />
<Hostname>server2</Hostname>
<PortChannelInterfaces />
<SubInterfaces />
<VlanInterfaces />
<IPInterfaces />
<DataAcls />
<AclInterfaces />
<NatInterfaces xmlns:a="http://schemas.datacontract.org/2004/07/Microsoft.Search.Autopilot.Evolution" />
<DownstreamSummaries />
<DownstreamSummarySet xmlns:a="http://schemas.datacontract.org/2004/07/Microsoft.Search.Autopilot.Evolution" />
</DeviceDataPlaneInfo>
</DpgDec> </DpgDec>
<PngDec> <PngDec>
<DeviceInterfaceLinks> <DeviceInterfaceLinks>
@ -262,17 +318,6 @@
<StartPort>L</StartPort> <StartPort>L</StartPort>
<Validate>true</Validate> <Validate>true</Validate>
</DeviceLinkBase> </DeviceLinkBase>
<DeviceLinkBase i:type="LogicalLink">
<ElementType>LogicalLink</ElementType>
<Bandwidth>10000</Bandwidth>
<ChassisInternal>false</ChassisInternal>
<EndDevice>switch-t0</EndDevice>
<EndPort>fortyGigE0/8</EndPort>
<FlowControl>true</FlowControl>
<StartDevice>server2-SC</StartDevice>
<StartPort>U</StartPort>
<Validate>true</Validate>
</DeviceLinkBase>
<DeviceLinkBase i:type="LogicalLink"> <DeviceLinkBase i:type="LogicalLink">
<ElementType>LogicalLink</ElementType> <ElementType>LogicalLink</ElementType>
<Bandwidth>0</Bandwidth> <Bandwidth>0</Bandwidth>
@ -349,25 +394,6 @@
<Hostname>server1</Hostname> <Hostname>server1</Hostname>
<HwSku>server-sku</HwSku> <HwSku>server-sku</HwSku>
</Device> </Device>
<Device i:type="SmartCable">
<ElementType>SmartCable</ElementType>
<SubType>active-active</SubType>
<Address xmlns:d5p1="Microsoft.Search.Autopilot.NetMux">
<d5p1:IPPrefix>10.10.10.3/32</d5p1:IPPrefix>
</Address>
<AddressV6 xmlns:d5p1="Microsoft.Search.Autopilot.NetMux">
<d5p1:IPPrefix>::/0</d5p1:IPPrefix>
</AddressV6>
<ManagementAddress xmlns:d5p1="Microsoft.Search.Autopilot.NetMux">
<d5p1:IPPrefix>0.0.0.0/0</d5p1:IPPrefix>
</ManagementAddress>
<ManagementAddressV6 xmlns:d5p1="Microsoft.Search.Autopilot.NetMux">
<d5p1:IPPrefix>::/0</d5p1:IPPrefix>
</ManagementAddressV6>
<SerialNumber i:nil="true" />
<Hostname>server2-SC</Hostname>
<HwSku>smartcable-sku</HwSku>
</Device>
<Device i:type="Server"> <Device i:type="Server">
<ElementType>Server</ElementType> <ElementType>Server</ElementType>
<Address xmlns:d5p1="Microsoft.Search.Autopilot.NetMux"> <Address xmlns:d5p1="Microsoft.Search.Autopilot.NetMux">
@ -506,6 +532,11 @@
<a:Reference i:nil="true"/> <a:Reference i:nil="true"/>
<a:Value>Storage</a:Value> <a:Value>Storage</a:Value>
</a:DeviceProperty> </a:DeviceProperty>
<a:DeviceProperty>
<a:Name>RedundancyType</a:Name>
<a:Reference i:nil="true"/>
<a:Value>Mixed</a:Value>
</a:DeviceProperty>
</a:Properties> </a:Properties>
</a:DeviceMetadata> </a:DeviceMetadata>
</Devices> </Devices>

View File

@ -236,14 +236,6 @@ class TestCfgGenCaseInsensitive(TestCase):
'lo_addr_v6': '::/0', 'lo_addr_v6': '::/0',
'mgmt_addr': '0.0.0.0/0', 'mgmt_addr': '0.0.0.0/0',
'type': 'SmartCable' '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) output = self.run_script(argument)
@ -421,6 +413,7 @@ class TestCfgGenCaseInsensitive(TestCase):
'server_ipv4': '10.10.10.2/32', 'server_ipv4': '10.10.10.2/32',
'server_ipv6': 'fe80::2/128', 'server_ipv6': 'fe80::2/128',
'soc_ipv4': '10.10.10.3/32', 'soc_ipv4': '10.10.10.3/32',
'soc_ipv6': 'fe80::3/128',
'cable_type': 'active-active' 'cable_type': 'active-active'
} }
} }