Skip to content

Commit

Permalink
[Test gap] Test dhcp_relay with source port ip enabled (sonic-net#14653)
Browse files Browse the repository at this point in the history
Description of PR
Summary:
Fixes sonic-net#3624
Mitigate the test gap: Test dhcp relay with source port ip in relay enabled.

Approach
What is the motivation for this PR?
Fixes sonic-net#3624
Mitigate the test gap: Test dhcp relay with source port ip in relay enabled.

How did you do it?
Enhance dhcp_relay ptf test to verify the src_ip in relay packets
Add a fixture which modify deployment_id to 8 and enable source port ip in relay
Add a test case exactly same with test_dhcp_relay_default but include fixture enable_source_port_ip_in_relay.
How did you verify/test it?
Run on local dev vm,
dhcp_relay/test_dhcp_relay.py::test_interface_binding PASSED [ 12%]
dhcp_relay/test_dhcp_relay.py::test_dhcp_relay_default PASSED [ 25%]
dhcp_relay/test_dhcp_relay.py::test_dhcp_relay_with_source_port_ip_in_relay_enabled PASSED [ 37%]
dhcp_relay/test_dhcp_relay.py::test_dhcp_relay_after_link_flap PASSED [ 50%]
dhcp_relay/test_dhcp_relay.py::test_dhcp_relay_start_with_uplinks_down PASSED [ 62%]
dhcp_relay/test_dhcp_relay.py::test_dhcp_relay_unicast_mac PASSED [ 75%]
dhcp_relay/test_dhcp_relay.py::test_dhcp_relay_random_sport PASSED [ 87%]
dhcp_relay/test_dhcp_relay.py::test_dhcp_relay_counter SKIPPED (skip...) [100%]
and PR test will test it again.

co-authorized by: jianquanye@microsoft.com
  • Loading branch information
yejianquan authored and arista-hpandya committed Oct 2, 2024
1 parent 3ddd293 commit 3112407
Show file tree
Hide file tree
Showing 3 changed files with 298 additions and 4 deletions.
16 changes: 12 additions & 4 deletions ansible/roles/test/files/ptftests/py3/dhcp_relay_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ def setUp(self):

self.dest_mac_address = self.test_params['dest_mac_address']
self.client_udp_src_port = self.test_params['client_udp_src_port']
self.enable_source_port_ip_in_relay = self.test_params.get('enable_source_port_ip_in_relay', False)

def tearDown(self):
DataplaneBaseTest.tearDown(self)
Expand Down Expand Up @@ -229,7 +230,12 @@ def create_dhcp_discover_relayed_packet(self):
# be loopback. We could pull from minigraph and check here.
ether = scapy.Ether(dst=self.BROADCAST_MAC,
src=self.uplink_mac, type=0x0800)
ip = scapy.IP(src=self.DEFAULT_ROUTE_IP,

source_ip = self.switch_loopback_ip
if self.enable_source_port_ip_in_relay:
source_ip = self.relay_iface_ip

ip = scapy.IP(src=source_ip,
dst=self.BROADCAST_IP, len=328, ttl=64)
udp = scapy.UDP(sport=self.DHCP_SERVER_PORT,
dport=self.DHCP_SERVER_PORT, len=308)
Expand Down Expand Up @@ -420,7 +426,11 @@ def create_dhcp_request_relayed_packet(self):
# be loopback. We could pull from minigraph and check here.
ether = scapy.Ether(dst=self.BROADCAST_MAC,
src=self.uplink_mac, type=0x0800)
ip = scapy.IP(src=self.DEFAULT_ROUTE_IP,

source_ip = self.switch_loopback_ip
if self.enable_source_port_ip_in_relay:
source_ip = self.relay_iface_ip
ip = scapy.IP(src=source_ip,
dst=self.BROADCAST_IP, len=336, ttl=64)
udp = scapy.UDP(sport=self.DHCP_SERVER_PORT,
dport=self.DHCP_SERVER_PORT, len=316)
Expand Down Expand Up @@ -562,7 +572,6 @@ def verify_relayed_discover(self):
masked_discover.set_do_not_care_scapy(scapy.IP, "ttl")
masked_discover.set_do_not_care_scapy(scapy.IP, "proto")
masked_discover.set_do_not_care_scapy(scapy.IP, "chksum")
masked_discover.set_do_not_care_scapy(scapy.IP, "src")
masked_discover.set_do_not_care_scapy(scapy.IP, "dst")
masked_discover.set_do_not_care_scapy(scapy.IP, "options")

Expand Down Expand Up @@ -639,7 +648,6 @@ def verify_relayed_request(self):
masked_request.set_do_not_care_scapy(scapy.IP, "ttl")
masked_request.set_do_not_care_scapy(scapy.IP, "proto")
masked_request.set_do_not_care_scapy(scapy.IP, "chksum")
masked_request.set_do_not_care_scapy(scapy.IP, "src")
masked_request.set_do_not_care_scapy(scapy.IP, "dst")
masked_request.set_do_not_care_scapy(scapy.IP, "options")

Expand Down
147 changes: 147 additions & 0 deletions tests/common/gcu_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import json
import logging

import pytest

from tests.common import config_reload
from tests.common.helpers.assertions import pytest_assert

logger = logging.getLogger(__name__)
DEFAULT_CHECKPOINT_NAME = "test"


def generate_tmpfile(duthost):
"""Generate temp file
"""
return duthost.shell('mktemp')['stdout']


def apply_patch(duthost, json_data, dest_file):
"""Run apply-patch on target duthost
Args:
duthost: Device Under Test (DUT)
json_data: Source json patch to apply
dest_file: Destination file on duthost
"""
duthost.copy(content=json.dumps(json_data, indent=4), dest=dest_file)

cmds = 'config apply-patch {}'.format(dest_file)

logger.info("Commands: {}".format(cmds))
output = duthost.shell(cmds, module_ignore_errors=True)

return output


def delete_tmpfile(duthost, tmpfile):
"""Delete temp file
"""
duthost.file(path=tmpfile, state='absent')


def create_checkpoint(duthost, cp=DEFAULT_CHECKPOINT_NAME):
"""Run checkpoint on target duthost
Args:
duthost: Device Under Test (DUT)
cp: checkpoint filename
"""
cmds = 'config checkpoint {}'.format(cp)

logger.info("Commands: {}".format(cmds))
output = duthost.shell(cmds, module_ignore_errors=True)

pytest_assert(
not output['rc']
and "Checkpoint created successfully" in output['stdout']
and verify_checkpoints_exist(duthost, cp),
"Failed to config a checkpoint file: {}".format(cp)
)


def list_checkpoints(duthost):
"""List checkpoint on target duthost
Args:
duthost: Device Under Test (DUT)
"""
cmds = 'config list-checkpoints'

logger.info("Commands: {}".format(cmds))
output = duthost.shell(cmds, module_ignore_errors=True)

pytest_assert(
not output['rc'],
"Failed to list all checkpoint file"
)

return output


def verify_checkpoints_exist(duthost, cp):
"""Check if checkpoint file exist in duthost
"""
output = list_checkpoints(duthost)
return '"{}"'.format(cp) in output['stdout']


def rollback(duthost, cp=DEFAULT_CHECKPOINT_NAME):
"""Run rollback on target duthost
Args:
duthost: Device Under Test (DUT)
cp: rollback filename
"""
cmds = 'config rollback {}'.format(cp)

logger.info("Commands: {}".format(cmds))
output = duthost.shell(cmds, module_ignore_errors=True)

return output


def rollback_or_reload(duthost, cp=DEFAULT_CHECKPOINT_NAME):
"""Run rollback on target duthost. config_reload if rollback failed.
Args:
duthost: Device Under Test (DUT)
"""
output = rollback(duthost, cp)

if output['rc'] or "Config rolled back successfully" not in output['stdout']:
config_reload(duthost)
pytest.fail("config rollback failed. Restored by config_reload")


def delete_checkpoint(duthost, cp=DEFAULT_CHECKPOINT_NAME):
"""Run checkpoint on target duthost
Args:
duthost: Device Under Test (DUT)
cp: checkpoint filename
"""
pytest_assert(
verify_checkpoints_exist(duthost, cp),
"Failed to find the checkpoint file: {}".format(cp)
)

cmds = 'config delete-checkpoint {}'.format(cp)

logger.info("Commands: {}".format(cmds))
output = duthost.shell(cmds, module_ignore_errors=True)

pytest_assert(
not output['rc'] and "Checkpoint deleted successfully" in output['stdout'],
"Failed to delete a checkpoint file: {}".format(cp)
)


def expect_op_success(duthost, output):
"""Expected success from apply-patch output
"""
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"
)
139 changes: 139 additions & 0 deletions tests/dhcp_relay/test_dhcp_relay.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
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.dualtor.mux_simulator_control import toggle_all_simulator_ports_to_rand_selected_tor_m # noqa F401
from tests.common.gcu_utils import generate_tmpfile, create_checkpoint, \
apply_patch, expect_op_success, delete_tmpfile, \
rollback_or_reload, delete_checkpoint
from tests.ptf_runner import ptf_runner
from tests.common.utilities import wait_until
from tests.common.helpers.dut_utils import check_link_status
Expand Down Expand Up @@ -52,6 +55,53 @@ def check_interface_status(duthost):
return False


@pytest.fixture(scope="function")
def enable_source_port_ip_in_relay(duthosts, rand_one_dut_hostname, tbinfo):
duthost = duthosts[rand_one_dut_hostname]

"""
Enable source port ip in relay function
-si parameter(Enable source port ip in relay function) will be added if deployment_id is '8', ref:
https://github.com/sonic-net/sonic-buildimage/blob/e0e0c0c1b3c58635bc25fde6a77ca3b0849dfde1/dockers/docker-dhcp-relay/dhcpv4-relay.agents.j2#L16
"""

json_patch = [
{
"op": "replace",
"path": "/DEVICE_METADATA/localhost/deployment_id",
"value": "8"
}
]

tmpfile = generate_tmpfile(duthost)
logger.info("tmpfile {}".format(tmpfile))
check_point = "dhcp_relay"
try:
create_checkpoint(duthost, check_point)
output = apply_patch(duthost, json_data=json_patch, dest_file=tmpfile)
expect_op_success(duthost, output)
duthost.restart_service("dhcp_relay")

def dhcp_ready(enable_source_port_ip_in_relay):
dhcp_relay_running = duthost.is_service_fully_started("dhcp_relay")
dhcp_relay_process = duthost.shell("ps -ef |grep dhcrelay|grep -v grep",
module_ignore_errors=True)["stdout"]
if enable_source_port_ip_in_relay:
dhcp_relay_process_ready = "-si" in dhcp_relay_process and "dhcrelay" in dhcp_relay_process
else:
dhcp_relay_process_ready = "-si" not in dhcp_relay_process and "dhcrelay" in dhcp_relay_process
return dhcp_relay_running and dhcp_relay_process_ready
pytest_assert(wait_until(60, 2, 0, dhcp_ready, True), "Source port ip in relay is not enabled!")
yield
finally:
delete_tmpfile(duthost, tmpfile)
logger.info("Rolled back to original checkpoint")
rollback_or_reload(duthost, check_point)
delete_checkpoint(duthost, check_point)
duthost.restart_service("dhcp_relay")
pytest_assert(wait_until(60, 2, 0, dhcp_ready, False), "Source port ip in relay is not disabled!")


def test_interface_binding(duthosts, rand_one_dut_hostname, dut_dhcp_relay_data):
duthost = duthosts[rand_one_dut_hostname]
skip_release(duthost, ["201811", "201911", "202106"])
Expand Down Expand Up @@ -184,6 +234,95 @@ def test_dhcp_relay_default(ptfhost, dut_dhcp_relay_data, validate_dut_routes_ex
pytest_assert(wait_until(120, 5, 0, check_interface_status, duthost))


def test_dhcp_relay_with_source_port_ip_in_relay_enabled(ptfhost, dut_dhcp_relay_data,
validate_dut_routes_exist, testing_config,
setup_standby_ports_on_rand_unselected_tor, # noqa F811
rand_unselected_dut, toggle_all_simulator_ports_to_rand_selected_tor_m, # noqa F811
enable_source_port_ip_in_relay):
"""Test DHCP relay functionality on T0 topology.
For each DHCP relay agent running on the DuT, verify DHCP packets are relayed properly
"""
testing_mode, duthost = testing_config

if testing_mode == DUAL_TOR_MODE:
skip_release(duthost, ["201811", "201911"])

skip_dhcpmon = any(vers in duthost.os_version for vers in ["201811", "201911", "202111"])

try:
for dhcp_relay in dut_dhcp_relay_data:
if not skip_dhcpmon:
dhcp_server_num = len(dhcp_relay['downlink_vlan_iface']['dhcp_server_addrs'])
if testing_mode == DUAL_TOR_MODE:
standby_duthost = rand_unselected_dut
start_dhcp_monitor_debug_counter(standby_duthost)
expected_standby_agg_counter_message = (
r".*dhcp_relay#dhcpmon\[[0-9]+\]: "
r"\[\s*Agg-%s\s*-[\sA-Za-z0-9]+\s*rx/tx\] "
r"Discover: +0/ +0, Offer: +0/ +0, Request: +0/ +0, ACK: +0/ +0+"
) % (dhcp_relay['downlink_vlan_iface']['name'])
loganalyzer_standby = LogAnalyzer(ansible_host=standby_duthost, marker_prefix="dhcpmon counter")
marker_standby = loganalyzer_standby.init()
loganalyzer_standby.expect_regex = [expected_standby_agg_counter_message]
start_dhcp_monitor_debug_counter(duthost)
if testing_mode == DUAL_TOR_MODE:
expected_agg_counter_message = (
r".*dhcp_relay#dhcpmon\[[0-9]+\]: "
r"\[\s*Agg-%s\s*-[\sA-Za-z0-9]+\s*rx/tx\] "
r"Discover: +1/ +%d, Offer: +1/ +1, Request: +1/ +%d, ACK: +1/ +1+"
) % (dhcp_relay['downlink_vlan_iface']['name'], dhcp_server_num, dhcp_server_num)
else:
expected_agg_counter_message = (
r".*dhcp_relay#dhcpmon\[[0-9]+\]: "
r"\[\s*Agg-%s\s*-[\sA-Za-z0-9]+\s*rx/tx\] "
r"Discover: +1/ +%d, Offer: +1/ +1, Request: +2/ +%d, ACK: +1/ +1+"
) % (dhcp_relay['downlink_vlan_iface']['name'], dhcp_server_num, dhcp_server_num * 2)
loganalyzer = LogAnalyzer(ansible_host=duthost, marker_prefix="dhcpmon counter")
marker = loganalyzer.init()
loganalyzer.expect_regex = [expected_agg_counter_message]

# Run the DHCP relay test on the PTF host
ptf_runner(ptfhost,
"ptftests",
"dhcp_relay_test.DHCPTest",
platform_dir="ptftests",
params={"hostname": duthost.hostname,
"client_port_index": dhcp_relay['client_iface']['port_idx'],
# This port is introduced to test DHCP relay packet received
# on other client port
"other_client_port": repr(dhcp_relay['other_client_ports']),
"client_iface_alias": str(dhcp_relay['client_iface']['alias']),
"leaf_port_indices": repr(dhcp_relay['uplink_port_indices']),
"num_dhcp_servers": len(dhcp_relay['downlink_vlan_iface']['dhcp_server_addrs']),
"server_ip": dhcp_relay['downlink_vlan_iface']['dhcp_server_addrs'],
"relay_iface_ip": str(dhcp_relay['downlink_vlan_iface']['addr']),
"relay_iface_mac": str(dhcp_relay['downlink_vlan_iface']['mac']),
"relay_iface_netmask": str(dhcp_relay['downlink_vlan_iface']['mask']),
"dest_mac_address": BROADCAST_MAC,
"client_udp_src_port": DEFAULT_DHCP_CLIENT_PORT,
"switch_loopback_ip": dhcp_relay['switch_loopback_ip'],
"uplink_mac": str(dhcp_relay['uplink_mac']),
"testing_mode": testing_mode,
"enable_source_port_ip_in_relay": True},
log_file="/tmp/dhcp_relay_test.DHCPTest.log", is_python3=True)
if not skip_dhcpmon:
time.sleep(36) # dhcpmon debug counter prints every 18 seconds
loganalyzer.analyze(marker)
if testing_mode == DUAL_TOR_MODE:
loganalyzer_standby.analyze(marker_standby)
except LogAnalyzerError as err:
logger.error("Unable to find expected log in syslog")
raise err

if not skip_dhcpmon:
# Clean up - Restart DHCP relay service on DUT to recover original dhcpmon setting
restart_dhcp_service(duthost)
if testing_mode == DUAL_TOR_MODE:
restart_dhcp_service(standby_duthost)
pytest_assert(wait_until(120, 5, 0, check_interface_status, standby_duthost))
pytest_assert(wait_until(120, 5, 0, check_interface_status, duthost))


def test_dhcp_relay_after_link_flap(ptfhost, dut_dhcp_relay_data, validate_dut_routes_exist, testing_config):
"""Test DHCP relay functionality on T0 topology after uplinks flap
For each DHCP relay agent running on the DuT, with relay agent running, flap the uplinks,
Expand Down

0 comments on commit 3112407

Please sign in to comment.