Skip to content

Commit

Permalink
[dualtor-aa] add test to verify iptables nat rules for mux port (#14919)
Browse files Browse the repository at this point in the history
Covers test gap #11436 which is raised to cover this issue sonic-net/sonic-host-services#95. The test will check nat rules and make sure vlan ip to mux cable soc_ip snat is correct, and in the case of multivlan, only vlan ip of the vlan as soc_ip is used.
  • Loading branch information
Xichen96 authored Dec 24, 2024
1 parent c7bbc1f commit d62e7a1
Show file tree
Hide file tree
Showing 10 changed files with 421 additions and 44 deletions.
2 changes: 2 additions & 0 deletions ansible/config_sonic_basedon_testbed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,12 +179,14 @@
vm_config: "{{ vm_topo_config }}"
port_alias: "{{ port_alias }}"
vlan_intfs: "{{ vlan_intfs }}"
vlan_config: "{{ vlan_config | default(None) }}"
delegate_to: localhost
when: "'dualtor' in topo or 'cable' in topo"

- name: gather mux cable information
mux_cable_facts:
topo_name: "{{ topo }}"
vlan_config: "{{ vlan_config | default(None) }}"
delegate_to: localhost
when: "'dualtor' in topo"

Expand Down
13 changes: 8 additions & 5 deletions ansible/library/dual_tor_facts.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

def load_topo_file(topo_name):
"""Load topo definition yaml file."""
topo_file = "vars/topo_%s.yml" % topo_name
topo_file = "../ansible/vars/topo_%s.yml" % topo_name
if not os.path.exists(topo_file):
raise ValueError("Topo file %s not exists" % topo_file)
with open(topo_file) as fd:
Expand All @@ -24,13 +24,14 @@ def load_topo_file(topo_name):

class DualTorParser:

def __init__(self, hostname, testbed_facts, host_vars, vm_config, port_alias, vlan_intfs):
def __init__(self, hostname, testbed_facts, host_vars, vm_config, port_alias, vlan_intfs, vlan_config):
self.hostname = hostname
self.testbed_facts = testbed_facts
self.host_vars = host_vars
self.vm_config = vm_config
self.port_alias = port_alias
self.vlan_intfs = vlan_intfs
self.vlan_config = vlan_config
self.dual_tor_facts = {}

def parse_neighbor_tor(self):
Expand Down Expand Up @@ -94,7 +95,7 @@ def generate_mux_cable_facts(self):
topo_name = self.testbed_facts["topo"]

topology = load_topo_file(topo_name)["topology"]
mux_cable_facts = generate_mux_cable_facts(topology=topology)
mux_cable_facts = generate_mux_cable_facts(topology=topology, vlan_config=self.vlan_config)
self.dual_tor_facts["mux_cable_facts"] = mux_cable_facts

def get_dual_tor_facts(self):
Expand All @@ -119,7 +120,8 @@ def main():
hostvars=dict(required=True, default=None, type='dict'),
vm_config=dict(required=True, default=None, type='dict'),
port_alias=dict(required=True, default=None, type='list'),
vlan_intfs=dict(required=True, default=None, type='list')
vlan_intfs=dict(required=True, default=None, type='list'),
vlan_config=dict(required=False, default=None, type='str'),
),
supports_check_mode=True
)
Expand All @@ -135,9 +137,10 @@ def main():
vm_config = m_args['vm_config']
port_alias = m_args['port_alias']
vlan_intfs = m_args['vlan_intfs']
vlan_config = m_args['vlan_config']
try:
dual_tor_parser = DualTorParser(
hostname, testbed_facts, host_vars, vm_config, port_alias, vlan_intfs)
hostname, testbed_facts, host_vars, vm_config, port_alias, vlan_intfs, vlan_config)
module.exit_json(
ansible_facts={'dual_tor_facts': dual_tor_parser.get_dual_tor_facts()})
except Exception:
Expand Down
5 changes: 3 additions & 2 deletions ansible/library/mux_cable_facts.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

def load_topo_file(topo_name):
"""Load topo definition yaml file."""
topo_file = "vars/topo_%s.yml" % topo_name
topo_file = "../ansible/vars/topo_%s.yml" % topo_name
if not os.path.exists(topo_file):
raise ValueError("Topo file %s not exists" % topo_file)
with open(topo_file) as fd:
Expand All @@ -42,6 +42,7 @@ def main():
argument_spec=dict(
topo_name=dict(required=False, type="str"),
topology=dict(required=False, type="dict"),
vlan_config=dict(required=False, default=None, type="str"),
),
mutually_exclusive=[["topo_name", "topology"]],
required_one_of=[["topo_name", "topology"]]
Expand All @@ -54,7 +55,7 @@ def main():
topology = args["topology"]

try:
mux_cable_facts = generate_mux_cable_facts(topology=topology)
mux_cable_facts = generate_mux_cable_facts(topology=topology, vlan_config=args["vlan_config"])
module.exit_json(ansible_facts={"mux_cable_facts": mux_cable_facts})
except Exception:
module.fail_json(msg=traceback.format_exc())
Expand Down
4 changes: 2 additions & 2 deletions ansible/library/test_facts.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@
'''

# Default testbed file name
TESTBED_FILE = 'testbed.yaml'
TESTCASE_FILE = 'roles/test/vars/testcases.yml'
TESTBED_FILE = '../ansible/testbed.yaml'
TESTCASE_FILE = '../ansible/roles/test/vars/testcases.yml'


class ParseTestbedTopoinfo():
Expand Down
10 changes: 6 additions & 4 deletions ansible/library/topo_facts.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
required: True
'''

VARS_PATH = "../ansible/vars/"


def parse_vm_vlan_port(vlan):
"""
Expand Down Expand Up @@ -246,14 +248,14 @@ def get_topo_config(self, topo_name, hwsku, testbed_name, asics_present, card_ty
if 'ptf64' in topo_name:
topo_name = 't1-64'
topo_name = re.sub(CLET_SUFFIX + "$", "", topo_name)
topo_filename = 'vars/topo_' + topo_name + '.yml'
topo_filename = VARS_PATH + 'topo_' + topo_name + '.yml'

asic_topo_file_candidate_list = []

if testbed_name:
asic_topo_file_candidate_list.append(
'vars/' + testbed_name + '/topo_' + hwsku + '.yml')
asic_topo_file_candidate_list.append('vars/topo_' + hwsku + '.yml')
VARS_PATH + testbed_name + '/topo_' + hwsku + '.yml')
asic_topo_file_candidate_list.append(VARS_PATH + 'topo_' + hwsku + '.yml')
vm_topo_config = dict()
vm_topo_config['topo_type'] = None
asic_topo_config = dict()
Expand All @@ -268,7 +270,7 @@ def get_topo_config(self, topo_name, hwsku, testbed_name, asics_present, card_ty
# read topology definition
if not os.path.isfile(topo_filename):
raise Exception(
"cannot find topology definition file under vars/topo_%s.yml file!" % topo_name)
"cannot find topology definition file under ../ansible/vars/topo_%s.yml file!" % topo_name)
else:
with open(topo_filename) as f:
topo_definition = yaml.safe_load(f)
Expand Down
61 changes: 30 additions & 31 deletions ansible/module_utils/dualtor_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,41 +13,40 @@ def get_intf_index(intf):
return intf


def generate_mux_cable_facts(topology):
def generate_mux_cable_facts(topology, vlan_config=None):
"""Generate mux cable table facts for dualtor topology."""
mux_cable_facts = {}
host_interfaces = set(get_intf_index(_) for _ in topology.get("host_interfaces", []))
disabled_host_interfaces = set(get_intf_index(_) for _ in topology.get("disabled_host_interfaces", []))
host_interfaces_active_active = set(get_intf_index(_) for _ in topology.get("host_interfaces_active_active", []))
enabled_interfaces = sorted(list(host_interfaces - disabled_host_interfaces))

vlan_config = list(
topology["DUT"]["vlan_configs"][topology["DUT"]["vlan_configs"]["default_vlan_config"]].values())[0]
if not vlan_config:
vlan_config = topology["DUT"]["vlan_configs"]["default_vlan_config"]
vlan_configs = topology["DUT"]["vlan_configs"][vlan_config]
# NOTE: vlan prefix will be 192.168.0.1/21 and fc02:1000::1/64
vlan_prefix_v4 = vlan_config["prefix"]
vlan_prefix_v6 = vlan_config["prefix_v6"]
vlan_address_v4, netmask_v4 = vlan_prefix_v4.split("/")
vlan_address_v6, netmask_v6 = vlan_prefix_v6.split("/")
vlan_address_v4 = ipaddress.ip_address(six.text_type(vlan_address_v4))
vlan_address_v6 = ipaddress.ip_address(six.text_type(vlan_address_v6))
for index, intf in enumerate(enabled_interfaces):
if host_interfaces_active_active:
is_active_active = intf in host_interfaces_active_active
# server IPs should be even-numbered
mux_cable_facts[intf] = dict(
server_ipv4=str(vlan_address_v4 + index * 2 + 1) + "/" + netmask_v4,
server_ipv6=str(vlan_address_v6 + index * 2 + 1) + "/" + netmask_v6,
cable_type="active-standby"
)
if is_active_active:
mux_cable_facts[intf]["cable_type"] = "active-active"
# SoC IPs should be odd-numbered
mux_cable_facts[intf]["soc_ipv4"] = str(vlan_address_v4 + (index + 1) * 2) + "/" + netmask_v4
else:
mux_cable_facts[intf] = dict(
server_ipv4=str(vlan_address_v4 + index + 1) + "/" + netmask_v4,
server_ipv6=str(vlan_address_v6 + index + 1) + "/" + netmask_v6,
cable_type="active-standby"
)
for vlan, vlan_config in vlan_configs.items():
vlan_prefix_v4 = vlan_config["prefix"]
vlan_prefix_v6 = vlan_config["prefix_v6"]
vlan_address_v4, netmask_v4 = vlan_prefix_v4.split("/")
vlan_address_v6, netmask_v6 = vlan_prefix_v6.split("/")
vlan_address_v4 = ipaddress.ip_address(six.text_type(vlan_address_v4))
vlan_address_v6 = ipaddress.ip_address(six.text_type(vlan_address_v6))
for index, intf in enumerate(vlan_config["intfs"]):
if host_interfaces_active_active:
is_active_active = intf in host_interfaces_active_active
# server IPs should be even-numbered
mux_cable_facts[intf] = dict(
server_ipv4=str(vlan_address_v4 + index * 2 + 1) + "/" + netmask_v4,
server_ipv6=str(vlan_address_v6 + index * 2 + 1) + "/" + netmask_v6,
cable_type="active-standby"
)
if is_active_active:
mux_cable_facts[intf]["cable_type"] = "active-active"
# SoC IPs should be odd-numbered
mux_cable_facts[intf]["soc_ipv4"] = str(vlan_address_v4 + (index + 1) * 2) + "/" + netmask_v4
else:
mux_cable_facts[intf] = dict(
server_ipv4=str(vlan_address_v4 + index + 1) + "/" + netmask_v4,
server_ipv6=str(vlan_address_v6 + index + 1) + "/" + netmask_v6,
cable_type="active-standby"
)

return mux_cable_facts
73 changes: 73 additions & 0 deletions tests/dualtor/templates/multivlan_ip_template.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{% set vms=vm_topo_config['vm'].keys() | sort %}
{% set vms_number = vms | length %}
{% set dut_index = testbed_facts['duts_map'][inventory_hostname]|int %}
<IPInterfaces>
{% if (card_type is not defined or card_type != 'supervisor') and (vm_topo_config['topo_type'] != 'wan') %}
{% for index in range(vms_number) %}
{% if vm_topo_config['vm'][vms[index]]['ip_intf'][dut_index|int] is not none %}
<IPInterface>
<Name i:nil="true"/>
{% if 'port-channel' in vm_topo_config['vm'][vms[index]]['ip_intf'][dut_index|int]|lower %}
<AttachTo>PortChannel{{ '10' + ((index+1) |string) }}</AttachTo>
{% else %}
<AttachTo>{{ port_alias[vm_topo_config['vm'][vms[index]]['interface_indexes'][dut_index|int][0]] }}</AttachTo>
{% endif %}
<Prefix>{{ vm_topo_config['vm'][vms[index]]['bgp_ipv4'][dut_index|int] }}/{{ vm_topo_config['vm'][vms[index]]['ipv4mask'][dut_index|int] }}</Prefix>
</IPInterface>
<IPInterface>
<Name i:Name="true"/>
{% if 'port-channel' in vm_topo_config['vm'][vms[index]]['ip_intf'][dut_index|int]|lower %}
<AttachTo>PortChannel{{ '10' + ((index+1) |string) }}</AttachTo>
{% else %}
<AttachTo>{{ port_alias[vm_topo_config['vm'][vms[index]]['interface_indexes'][dut_index|int][0]] }}</AttachTo>
{% endif %}
<Prefix>{{ vm_topo_config['vm'][vms[index]]['bgp_ipv6'][dut_index|int] }}/{{ vm_topo_config['vm'][vms[index]]['ipv6mask'][dut_index|int] }}</Prefix>
</IPInterface>
{% endif %}
{% endfor %}
{% if 'tor' in vm_topo_config['dut_type'] | lower %}
{% for vlan, vlan_param in vlan_configs.items() %}
<IPInterface>
<Name i:nil="true"/>
<AttachTo>{{ vlan }}</AttachTo>
<Prefix>{{ vlan_param['prefix'] }}</Prefix>
</IPInterface>
{%if 'secondary_subnet' in vlan_param %}
<IPInterface>
<Name i:nil="true"/>
<AttachTo>{{ vlan }}</AttachTo>
<Prefix>{{ vlan_param['secondary_subnet'] }}</Prefix>
</IPInterface>
{% endif %}
{% endfor %}
{% for vlan, vlan_param in vlan_configs.items() %}
{% if 'prefix_v6' in vlan_param %}
<IPInterface>
<Name i:nil="true"/>
<AttachTo>{{ vlan }}</AttachTo>
<Prefix>{{ vlan_param['prefix_v6'] }}</Prefix>
</IPInterface>
{% endif %}
{% endfor %}
{% endif %}
{% elif vm_topo_config['topo_type'] == 'wan' and 'wan_dut_configuration' in vm_topo_config %}
{% for ifname, params in vm_topo_config['wan_dut_configuration'][dut_index|int]['interfaces'].items() %}
{% if ifname.startswith('PortChannel') %}
{% if 'ipv4' in params %}
<IPInterface>
<Name i:nil="true"/>
<AttachTo>{{ ifname }}</AttachTo>
<Prefix>{{ params['ipv4'] }}</Prefix>
</IPInterface>
{% endif %}
{% if 'ipv6' in params %}
<IPInterface>
<Name i:Name="true"/>
<AttachTo>{{ ifname }}</AttachTo>
<Prefix>{{ params['ipv6'] }}</Prefix>
</IPInterface>
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
</IPInterfaces>
67 changes: 67 additions & 0 deletions tests/dualtor/templates/multivlan_mux_config_template.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{% if mux_cable_facts is defined and mux_cable_facts %}
{% if dual_tor_facts is defined and 'cables' in dual_tor_facts %}
{% for cable in dual_tor_facts['cables'] %}
{% set intf_index = port_alias.index(cable['dut_intf'])|string %}
<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>{{ mux_cable_facts[intf_index]['server_ipv4'] }}</b:IPPrefix>
</a:Prefix>
<a:PrefixStr>{{ mux_cable_facts[intf_index]['server_ipv4'] }}</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>{{ mux_cable_facts[intf_index]['server_ipv6'] }}</b:IPPrefix>
</a:Prefix>
<a:PrefixStr>{{ mux_cable_facts[intf_index]['server_ipv6'] }}</a:PrefixStr>
</a:LoopbackIPInterface>
{% if 'soc_ipv4' in mux_cable_facts[intf_index] %}
<a:LoopbackIPInterface>
<ElementType>LoopbackInterface</ElementType>
<Name>SoCHostIP0</Name>
<AttachTo>Servers{{ loop.index - 1 }}SOC</AttachTo>
<a:Prefix xmlns:b="Microsoft.Search.Autopilot.NetMux">
<b:IPPrefix>{{ mux_cable_facts[intf_index]['soc_ipv4'] }}</b:IPPrefix>
</a:Prefix>
<a:PrefixStr>{{ mux_cable_facts[intf_index]['soc_ipv4'] }}</a:PrefixStr>
</a:LoopbackIPInterface>
{% endif %}
{% if 'soc_ipv6' in mux_cable_facts[intf_index] %}
<a:LoopbackIPInterface>
<ElementType>LoopbackInterface</ElementType>
<Name>SoCHostIP1</Name>
<AttachTo>Servers{{ loop.index - 1 }}SOC</AttachTo>
<a:Prefix xmlns:b="Microsoft.Search.Autopilot.NetMux">
<b:IPPrefix>{{ mux_cable_facts[intf_index]['soc_ipv6'] }}</b:IPPrefix>
</a:Prefix>
<a:PrefixStr>{{ mux_cable_facts[intf_index]['soc_ipv6'] }}</a:PrefixStr>
</a:LoopbackIPInterface>
{% endif %}
</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>Servers{{ loop.index - 1 }}</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>
{% endfor %}
{% endif %}
{% endif %}
35 changes: 35 additions & 0 deletions tests/dualtor/templates/multivlan_template.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<VlanInterfaces>
{% if 'tor' in vm_topo_config['dut_type'] | lower %}
{% for vlan, vlan_param in vlan_configs.items() %}
<VlanInterface>
<Name>{{ vlan }}</Name>
{% set vlan_intf_str=';'.join(vlan_param['intfs'] + vlan_param['portchannels']) %}
<AttachTo>{{ vlan_intf_str }}</AttachTo>
<NoDhcpRelay>False</NoDhcpRelay>
<StaticDHCPRelay>0.0.0.0/0</StaticDHCPRelay>
{% if 'type' in vlan_param %}
{% if vlan_param['type']|lower == 'tagged'%}
<Type>Tagged</Type>
{% else %}
<Type i:nil="true"/>
{% endif %}
{% endif %}
{% set dhcp_servers_str=';'.join(dhcp_servers) %}
<DhcpRelays>{{ dhcp_servers_str }}</DhcpRelays>
{% if dhcpv6_servers is defined %}
{% set dhcpv6_servers_str=';'.join(dhcpv6_servers) %}
<Dhcpv6Relays>{{ dhcpv6_servers_str }}</Dhcpv6Relays>
{% endif %}
<VlanID>{{ vlan_param['id'] }}</VlanID>
<Tag>{{ vlan_param['tag'] }}</Tag>
<Subnets>{{ vlan_param['prefix'] | ipaddr('network') }}/{{ vlan_param['prefix'] | ipaddr('prefix') }}</Subnets>
{% if 'secondary_subnet' in vlan_param %}
<SecondarySubnets>{{ vlan_param['secondary_subnet'] | ipaddr('network') }}/{{ vlan_param['secondary_subnet'] | ipaddr('secondary_subnet') }}<SecondarySubnets>
{% endif %}
{% if 'mac' in vlan_param %}
<MacAddress>{{ vlan_param['mac'] }}</MacAddress>
{% endif %}
</VlanInterface>
{% endfor %}
{% endif %}
</VlanInterfaces>
Loading

0 comments on commit d62e7a1

Please sign in to comment.