Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Test gap] Test dhcp_relay with source port ip enabled #14653

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -8,6 +8,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 @@ -225,6 +228,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 @@ -357,6 +407,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
Loading