Skip to content

Commit

Permalink
[dhcpv6_relay_test] test if dhcpv6_relay will set correct link addres…
Browse files Browse the repository at this point in the history
…s in packet on multiple vlans host (sonic-net#14732)

What is the motivation for this PR?
This code is to fix test gap: sonic-net#9122

How did you do it?
Configure multiple Vlans and dhcp servers
Send dhcp requests
Check dhcp packets relayed contains expected Vlan IP

How did you verify/test it?
Tested on mx, m0, dualtor and dualtor-aa.
  • Loading branch information
w1nda authored and sreejithsreekumaran committed Nov 15, 2024
1 parent 1ff7db8 commit 0fa8d2b
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 4 deletions.
4 changes: 0 additions & 4 deletions ansible/roles/test/files/ptftests/py3/dhcpv6_relay_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,8 +357,6 @@ def verify_relayed_solicit_relay_forward(self):
masked_packet.set_do_not_care_scapy(IPv6, "nh")
masked_packet.set_do_not_care_scapy(packet.UDP, "chksum")
masked_packet.set_do_not_care_scapy(packet.UDP, "len")
masked_packet.set_do_not_care_scapy(
scapy.layers.dhcp6.DHCP6_RelayForward, "linkaddr")
masked_packet.set_do_not_care_scapy(
DHCP6OptClientLinkLayerAddr, "clladdr")

Expand Down Expand Up @@ -414,8 +412,6 @@ def verify_relayed_request_relay_forward(self):
masked_packet.set_do_not_care_scapy(IPv6, "nh")
masked_packet.set_do_not_care_scapy(packet.UDP, "chksum")
masked_packet.set_do_not_care_scapy(packet.UDP, "len")
masked_packet.set_do_not_care_scapy(
scapy.layers.dhcp6.DHCP6_RelayForward, "linkaddr")
masked_packet.set_do_not_care_scapy(
DHCP6OptClientLinkLayerAddr, "clladdr")

Expand Down
223 changes: 223 additions & 0 deletions tests/common/fixtures/split_vlan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import pytest
import logging
import ipaddress
import json
import re
from math import log, ceil
from tests.common.helpers.assertions import pytest_assert
from tests.common.gcu_utils import create_checkpoint, rollback_or_reload, delete_checkpoint


@pytest.fixture(scope="class")
def setup_multiple_vlans_and_teardown(request, rand_selected_dut, rand_unselected_dut, tbinfo):
'''
This fixture will split the first vlan into multiple sub vlans and return the sub vlans info.
The count of sub vlans is determined by the parameter vlan_count which is passed to the test.
It will split prefix and members evenly to each sub vlan.
'''
vlan_count = request.param
is_dualtor = 'dualtor' in tbinfo['topo']['name']
duthost = rand_selected_dut
vlan_brief = duthost.get_vlan_brief()
first_vlan_name = list(vlan_brief.keys())[0]
first_vlan_info = list(vlan_brief.values())[0]
running_config = duthost.get_running_config_facts()
first_vlan_info['dhcp_servers'] = running_config['VLAN'][first_vlan_name].get('dhcp_servers', [])
first_vlan_info['dhcp_relay'] = running_config['DHCP_RELAY'].get(first_vlan_name, {}).get('dhcp_servers', [])
first_vlan_info['dhcpv6_servers'] = running_config['VLAN'][first_vlan_name].get('dhcpv6_servers', [])
first_vlan_info['dhcpv6_relay'] = running_config['DHCP_RELAY'].get(first_vlan_name, {}).get('dhcpv6_servers', [])
disabled_host_interfaces = tbinfo['topo']['properties']['topology'].get('disabled_host_interfaces', [])
connected_ptf_ports_idx = [interface for interface in
tbinfo['topo']['properties']['topology'].get('host_interfaces', [])
if interface not in disabled_host_interfaces]
if is_dualtor:
pattern = r'0.(\d+)'
connected_ptf_ports_idx = [int(re.findall(pattern, index)[0])
for index in connected_ptf_ports_idx if re.match(pattern, index)]
dut_intf_to_ptf_index = duthost.get_extended_minigraph_facts(tbinfo)['minigraph_ptf_indices']
connected_dut_intf_to_ptf_index = {k: v for k, v in dut_intf_to_ptf_index.items() if v in connected_ptf_ports_idx}
vlan_members = first_vlan_info['members']
vlan_member_with_ptf_idx = [(member, connected_dut_intf_to_ptf_index[member])
for member in vlan_members if member in connected_dut_intf_to_ptf_index]
logging.info("The first_vlan_info before test is %s" % first_vlan_info)
sub_vlans_info, config_patch = generate_sub_vlans_config_patch(
first_vlan_name,
first_vlan_info,
vlan_member_with_ptf_idx,
vlan_count
)
try:
checkpoint_name = 'mutiple_vlans_test'
create_checkpoint(duthost, checkpoint_name)

logging.info("The patch for setup is %s" % config_patch)
apply_config_patch(duthost, config_patch)
logging.info("The sub_vlans_info after setup is %s" % sub_vlans_info)
if is_dualtor:
create_checkpoint(rand_unselected_dut, checkpoint_name)
apply_config_patch(rand_unselected_dut, config_patch)

yield sub_vlans_info
finally:
rollback_or_reload(duthost, checkpoint_name)
delete_checkpoint(duthost, checkpoint_name)
if is_dualtor:
rollback_or_reload(rand_unselected_dut, checkpoint_name)
delete_checkpoint(rand_unselected_dut, checkpoint_name)


def generate_sub_vlans_config_patch(vlan_name, vlan_info, vlan_member_with_ptf_idx, count):
pytest_assert(len(vlan_info['interface_ipv4']) > 0, "Expected at least one ipv4 address prefix")
pytest_assert(len(vlan_info['interface_ipv6']) > 0, "Expected at least one ipv6 address prefix")
pytest_assert(len(vlan_member_with_ptf_idx) >= count, "Expected member count greater or equal to sub vlan count")

sub_vlans_info, config_patch = [], []
config_patch += remove_vlan_patch(vlan_name) \
+ remove_dhcpv6_relay_patch(vlan_name) \
+ [remove_vlan_ip_patch(vlan_name, ip)[0] for ip in vlan_info['interface_ipv4']] \
+ [remove_vlan_ip_patch(vlan_name, ip)[0] for ip in vlan_info['interface_ipv6']] \
+ [remove_vlan_member_patch(vlan_name, member)[0] for member in vlan_info['members']]

vlan_prefix_v4 = vlan_info['interface_ipv4'][0]
vlan_net_v4 = ipaddress.ip_network(address=vlan_prefix_v4, strict=False)
vlan_nets_v4 = list(vlan_net_v4.subnets(prefixlen_diff=int(ceil(log(count, 2)))))
vlan_prefix_v6 = vlan_info['interface_ipv6'][0]
vlan_net_v6 = ipaddress.ip_network(address=vlan_prefix_v6, strict=False)
vlan_nets_v6 = list(vlan_net_v6.subnets(prefixlen_diff=int(ceil(log(count, 2)))))
member_count = len(vlan_member_with_ptf_idx)//count
for i in range(count):
sub_vlans_info.append(
{
'vlan_name': 'Vlan90%s' % i,
'interface_ipv4': str(next(vlan_nets_v4[i].hosts())) + '/' + str(vlan_nets_v4[i].prefixlen),
'interface_ipv6': str(next(vlan_nets_v6[i].hosts())) + '/' + str(vlan_nets_v6[i].prefixlen),
'members_with_ptf_idx': [(member, ptf_idx) for member, ptf_idx
in vlan_member_with_ptf_idx[member_count*i:member_count*(i+1)]]
}
)

for info in sub_vlans_info:
new_vlan_name = info['vlan_name']
new_interface_ipv4 = info['interface_ipv4']
new_interface_ipv6 = info['interface_ipv6']
new_members_with_ptf_idx = info['members_with_ptf_idx']
config_patch += add_vlan_patch(new_vlan_name, vlan_info['dhcp_servers'], vlan_info['dhcpv6_servers']) \
+ add_dhcpv6_relay_patch(new_vlan_name, vlan_info['dhcpv6_relay']) \
+ add_vlan_ip_patch(new_vlan_name, new_interface_ipv4) \
+ add_vlan_ip_patch(new_vlan_name, new_interface_ipv6) \
+ [add_vlan_member_patch(new_vlan_name, member)[0] for member, _ in new_members_with_ptf_idx]

return sub_vlans_info, config_patch


def vlan_n2i(vlan_name):
"""
Convert vlan name to vlan id
"""
return vlan_name.replace("Vlan", "")


def add_vlan_patch(vlan_name, dhcp_servers, dhcpv6_servers):
patch = [
{
"op": "add",
"path": "/VLAN/%s" % vlan_name,
"value": {
"vlanid": vlan_n2i(vlan_name)
}
},
{
"op": "add",
"path": "/VLAN_INTERFACE/%s" % vlan_name,
"value": {}
}
]
if dhcp_servers:
patch[0]["value"]["dhcp_servers"] = dhcp_servers
if dhcpv6_servers:
patch[0]["value"]["dhcpv6_servers"] = dhcpv6_servers
return patch


def remove_vlan_patch(vlan_name):
patch = [
{
"op": "remove",
"path": "/VLAN/%s" % vlan_name
},
{
"op": "remove",
"path": "/VLAN_INTERFACE/%s" % vlan_name
}
]
return patch


def add_dhcpv6_relay_patch(vlan_name, dhcpv6_servers):
patch = [{
"op": "add",
"path": "/DHCP_RELAY/%s" % vlan_name,
"value": {}
}]
if dhcpv6_servers:
patch[0]["value"]["dhcpv6_servers"] = dhcpv6_servers
return patch


def remove_dhcpv6_relay_patch(vlan_name):
patch = [{
"op": "remove",
"path": "/DHCP_RELAY/%s" % vlan_name
}]
return patch


def add_vlan_member_patch(vlan_name, member_name):
patch = [{
"op": "add",
"path": "/VLAN_MEMBER/%s|%s" % (vlan_name, member_name),
"value": {
"tagging_mode": "untagged"
}
}]
return patch


def remove_vlan_member_patch(vlan_name, member_name):
patch = [{
"op": "remove",
"path": "/VLAN_MEMBER/%s|%s" % (vlan_name, member_name)
}]
return patch


def add_vlan_ip_patch(vlan_name, ip):
patch = [{
"op": "add",
"path": "/VLAN_INTERFACE/%s|%s" % (vlan_name, ip.replace('/', '~1')),
"value": {}
}]
return patch


def remove_vlan_ip_patch(vlan_name, ip):
patch = [{
"op": "remove",
"path": "/VLAN_INTERFACE/%s|%s" % (vlan_name, ip.replace('/', '~1'))
}]
return patch


def apply_config_patch(duthost, config_to_apply):
logging.info("The config patch: %s" % config_to_apply)
tmpfile = duthost.shell('mktemp')['stdout']
try:
duthost.copy(content=json.dumps(config_to_apply, indent=4), dest=tmpfile)
output = duthost.shell('config apply-patch {}'.format(tmpfile), module_ignore_errors=True)
pytest_assert(not output['rc'], "Command is not running successfully")
pytest_assert(
"Patch applied successfully" in output['stdout'],
"Please check if json file is validate"
)
finally:
duthost.file(path=tmpfile, state='absent')
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,12 @@ dhcp_relay/test_dhcpv6_relay.py:
conditions:
- "platform in ['x86_64-8111_32eh_o-r0']"

dhcp_relay/test_dhcpv6_relay.py::TestDhcpv6RelayWithMultipleVlan:
skip:
reason: "skip the multiple vlan test on aa dualtor as interface state is not aa after vlan split"
conditions:
- "'dualtor-aa' in topo_name"

#######################################
##### drop_packets #####
#######################################
Expand Down
57 changes: 57 additions & 0 deletions tests/dhcp_relay/test_dhcpv6_relay.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import ipaddress
import pytest
import random
import time
import netaddr
import logging

from tests.dhcp_relay.dhcp_relay_utils import restart_dhcp_service
from tests.common.fixtures.ptfhost_utils import copy_ptftests_directory # noqa F401
from tests.common.fixtures.ptfhost_utils import change_mac_addresses # noqa F401
from tests.common.fixtures.split_vlan import setup_multiple_vlans_and_teardown # noqa F401
from tests.common.utilities import skip_release
from tests.ptf_runner import ptf_runner
from tests.common import config_reload
Expand Down Expand Up @@ -487,3 +490,57 @@ def test_dhcp_relay_start_with_uplinks_down(ptfhost, dut_dhcp_relay_data, valida
"loopback_ipv6": str(dhcp_relay['loopback_ipv6']),
"is_dualtor": str(dhcp_relay['is_dualtor'])},
log_file="/tmp/dhcpv6_relay_test.DHCPTest.log", is_python3=True)


class TestDhcpv6RelayWithMultipleVlan:

@pytest.fixture(scope="class", autouse=True)
def restart_dhcp_relay_after_test(self, duthost):

yield
restart_dhcp_service(duthost)

@pytest.mark.parametrize("setup_multiple_vlans_and_teardown", [3], indirect=True)
def test_dhcp_relay_default(self, ptfhost, dut_dhcp_relay_data, validate_dut_routes_exist, testing_config,
toggle_all_simulator_ports_to_rand_selected_tor_m, # noqa F811
setup_active_active_as_active_standby, # noqa F811
setup_multiple_vlans_and_teardown): # noqa F811
'''
Test DHCP relay should set correct link address when relay packet to DHCP server
'''
vlans_info = setup_multiple_vlans_and_teardown
_, duthost = testing_config
# Please note: relay interface always means vlan interface
pytest_assert(len(dut_dhcp_relay_data) > 0, "No VLAN data")
common_dhcp_relay_data = dut_dhcp_relay_data[0]

restart_dhcp_service(duthost) # restart dhcp_relay to make new vlans config take into effect
for vlan_info in vlans_info:
vlan_name = vlan_info['vlan_name']
exp_link_addr = vlan_info['interface_ipv6'].split('/')[0]
_, ptf_port_index = random.choice(vlan_info['members_with_ptf_idx'])
logger.info("Randomly selected PTF port index: {}".format(ptf_port_index))
command = "ip addr show {} | grep inet6 | grep 'scope link' | awk '{{print $2}}' | cut -d '/' -f1" \
.format(vlan_name)
down_interface_link_local = duthost.shell(command)['stdout']
vlan_mac = duthost.shell('cat /sys/class/net/{}/address'.format(vlan_name))['stdout']
# Run the DHCP relay test on the PTF host
ptf_runner(ptfhost,
"ptftests",
"dhcpv6_relay_test.DHCPTest",
platform_dir="ptftests",
params={"hostname": duthost.hostname,
"client_port_index": ptf_port_index,
"leaf_port_indices": repr(common_dhcp_relay_data['uplink_port_indices']),
"num_dhcp_servers":
len(common_dhcp_relay_data['downlink_vlan_iface']['dhcpv6_server_addrs']),
"server_ip":
str(common_dhcp_relay_data['downlink_vlan_iface']['dhcpv6_server_addrs'][0]),
"relay_iface_ip": str(exp_link_addr),
"relay_iface_mac": str(vlan_mac),
"relay_link_local": str(down_interface_link_local),
"vlan_ip": str(exp_link_addr),
"uplink_mac": str(common_dhcp_relay_data['uplink_mac']),
"loopback_ipv6": str(common_dhcp_relay_data['loopback_ipv6']),
"is_dualtor": str(common_dhcp_relay_data['is_dualtor'])},
log_file="/tmp/dhcpv6_relay_test.DHCPTest.log", is_python3=True)

0 comments on commit 0fa8d2b

Please sign in to comment.