Skip to content

Commit

Permalink
Add test for NIC Passthrough
Browse files Browse the repository at this point in the history
Signed-off-by: Smit Gardhariya <[email protected]>
  • Loading branch information
smit-gardhariya committed Jan 9, 2025
1 parent 4b8bca8 commit 0176cf9
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 5 deletions.
3 changes: 3 additions & 0 deletions lisa/sut_orchestrator/libvirt/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ class NodeContext:
default_factory=list,
)

# Add host detail under node context for device-passthrough testcases
host_node: Any = None


def get_environment_context(environment: Environment) -> EnvironmentContext:
return environment.get_context(EnvironmentContext)
Expand Down
4 changes: 4 additions & 0 deletions lisa/sut_orchestrator/libvirt/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,10 @@ def _fill_nodes_metadata(self, environment: Environment, log: Logger) -> None:
)

node_context = get_node_context(node)
if self.host_node.is_remote:
node_context.host_node = remote_node
else:
node_context.host_node = self.host_node
if node_context.init_system == InitSystem.CLOUD_INIT:
# Ensure cloud-init completes its setup.
node.execute(
Expand Down
3 changes: 3 additions & 0 deletions lisa/tools/iperf3.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ def run_as_server_async(
use_json_format: bool = False,
one_connection_only: bool = False,
daemon: bool = True,
interface_ip: str = "",
) -> Process:
# -s: run iperf3 as server mode
# -D: run iperf3 as a daemon
Expand All @@ -135,6 +136,8 @@ def run_as_server_async(
cmd += f" -f {report_unit} "
if port:
cmd += f" -p {port} "
if interface_ip:
cmd += f" -B {interface_ip}"
process = self.node.execute_async(
f"{self.command} {cmd}", shell=True, sudo=True
)
Expand Down
30 changes: 27 additions & 3 deletions microsoft/testsuites/performance/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,17 +446,35 @@ def perf_iperf(
connections: List[int],
buffer_length_list: List[int],
udp_mode: bool = False,
server: Optional[RemoteNode] = None,
client: Optional[RemoteNode] = None,
run_server_on_internal_address: bool = False,
) -> None:
environment = test_result.environment
assert environment, "fail to get environment from testresult"

client = cast(RemoteNode, environment.nodes[0])
server = cast(RemoteNode, environment.nodes[1])
if server is not None or client is not None:
assert server is not None, "server need to be specified, if client is set"
assert client is not None, "client need to be specified, if server is set"
else:
environment = test_result.environment
assert environment, "fail to get environment from testresult"
# set server and client from environment, if not set explicitly
client = cast(RemoteNode, environment.nodes[0])
server = cast(RemoteNode, environment.nodes[1])

# Ensure that both server and client are non-None before accessing tools
assert client is not None, "client is None, cannot access tools"
assert server is not None, "server is None, cannot access tools"

client_iperf3, server_iperf3 = run_in_parallel(
[lambda: client.tools[Iperf3], lambda: server.tools[Iperf3]]
)
test_case_name = inspect.stack()[1][3]
iperf3_messages_list: List[Any] = []
server_interface_ip = ""
if run_server_on_internal_address:
server_interface_ip = server.internal_address
if udp_mode:
for node in [client, server]:
ssh = node.tools[Ssh]
Expand All @@ -481,7 +499,13 @@ def perf_iperf(
current_server_iperf_instances += 1
server_iperf3_process_list.append(
server_iperf3.run_as_server_async(
current_server_port, "g", 10, True, True, False
port=current_server_port,
report_unit="g",
report_periodic=10,
use_json_format=True,
one_connection_only=True,
daemon=False,
interface_ip=server_interface_ip,
)
)
current_server_port += 1
Expand Down
196 changes: 194 additions & 2 deletions microsoft/testsuites/performance/networkperf.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
import re
from functools import partial
from typing import Any
from typing import Any, Tuple, cast

from lisa import (
Logger,
RemoteNode,
TestCaseMetadata,
TestSuite,
TestSuiteMetadata,
Expand All @@ -16,15 +18,18 @@
from lisa.environment import Environment, Node
from lisa.features import Sriov, Synthetic
from lisa.operating_system import BSD, Windows
from lisa.sut_orchestrator import CLOUD_HYPERVISOR
from lisa.sut_orchestrator.libvirt.context import get_node_context
from lisa.testsuite import TestResult
from lisa.tools import Sysctl
from lisa.tools import Lspci, Sysctl
from lisa.tools.iperf3 import (
IPERF_TCP_BUFFER_LENGTHS,
IPERF_TCP_CONCURRENCY,
IPERF_UDP_BUFFER_LENGTHS,
IPERF_UDP_CONCURRENCY,
)
from lisa.tools.sockperf import SOCKPERF_TCP, SOCKPERF_UDP
from lisa.util import SkippedException, constants, find_group_in_lines
from lisa.util.parallel import run_in_parallel
from microsoft.testsuites.performance.common import (
cleanup_process,
Expand Down Expand Up @@ -450,3 +455,190 @@ def do_sysctl_cleanup(node: Node) -> None:
run_in_parallel(
[partial(do_sysctl_cleanup, x) for x in environment.nodes.list()]
)

@TestCaseMetadata(
description="""
This test case uses iperf3 to test passthrough tcp network throughput.
""",
priority=3,
timeout=TIMEOUT,
requirement=simple_requirement(
min_count=1,
supported_platform_type=[CLOUD_HYPERVISOR],
),
)
def perf_tcp_iperf_passthrough(
self,
node: Node,
result: TestResult,
) -> None:
# Run iperf server on VM and client on host
server, client = self._get_server_and_client_nodes(node)
perf_iperf(
test_result=result,
connections=IPERF_TCP_CONCURRENCY,
buffer_length_list=IPERF_TCP_BUFFER_LENGTHS,
server=server,
client=client,
run_server_on_internal_address=True,
)

@TestCaseMetadata(
description="""
This test case uses iperf3 to test passthrough udp network throughput.
""",
priority=3,
timeout=TIMEOUT,
requirement=simple_requirement(
min_count=1,
supported_platform_type=[CLOUD_HYPERVISOR],
),
)
def perf_udp_iperf_passthrough(
self,
node: Node,
result: TestResult,
) -> None:
# Run iperf server on VM and client on host
server, client = self._get_server_and_client_nodes(node)
perf_iperf(
test_result=result,
connections=IPERF_TCP_CONCURRENCY,
buffer_length_list=IPERF_TCP_BUFFER_LENGTHS,
server=server,
client=client,
udp_mode=True,
run_server_on_internal_address=True,
)

@TestCaseMetadata(
description="""
This test case uses sar to test passthrough network PPS (Packets Per Second)
when running netperf with single port. Test will consider VM as
server node and host as client node.
""",
priority=3,
timeout=PPS_TIMEOUT,
requirement=simple_requirement(
min_count=1,
supported_platform_type=[CLOUD_HYPERVISOR],
),
)
def perf_tcp_single_pps_passthrough(
self,
result: TestResult,
node: Node,
) -> None:
server, client = self._get_server_and_client_nodes(node)

perf_tcp_pps(
test_result=result,
test_type="singlepps",
server=server,
client=client,
)

@TestCaseMetadata(
description="""
This test case uses sar to test passthrough network PPS (Packets Per Second)
when running netperf with multiple ports. Test will consider VM as
server node and host as client node.
""",
priority=3,
timeout=PPS_TIMEOUT,
requirement=simple_requirement(
min_count=1,
supported_platform_type=[CLOUD_HYPERVISOR],
),
)
def perf_tcp_max_pps_passthrough(
self,
result: TestResult,
node: Node,
) -> None:
server, client = self._get_server_and_client_nodes(node)

perf_tcp_pps(
test_result=result,
test_type="maxpps",
server=server,
client=client,
)

def _get_server_and_client_nodes(
self,
node: Node,
) -> Tuple[RemoteNode, RemoteNode]:
ctx = get_node_context(node)
if not ctx.passthrough_devices:
raise SkippedException("No passthrough devices found for node")

# Configure the nw interface on guest
node.execute(
cmd="dhclient",
sudo=True,
expected_exit_code=0,
expected_exit_code_failure_message="dhclient command failed",
)

lspci = node.tools[Lspci]
pci_devices = lspci.get_devices_by_type(
constants.DEVICE_TYPE_SRIOV, force_run=True
)
device_addr = None

# Get the first non-virtio device
for device in pci_devices:
kernel_driver = lspci.get_used_module(device.slot)
if kernel_driver != "virtio-pci":
device_addr = device.slot
break
print(f"passthrough device: {device_addr}")

# Get the interface name
err_msg: str = "Can't find interface from PCI address"
device_path = node.execute(
cmd=(
"find /sys/class/net/*/device/subsystem/devices"
f" -name '*{device_addr}*'"
),
sudo=True,
shell=True,
expected_exit_code=0,
expected_exit_code_failure_message=err_msg,
).stdout

pattern = re.compile(r"/sys/class/net/(?P<INTERFACE_NAME>\w+)/device")
interface_name_raw = find_group_in_lines(
pattern=pattern,
lines=device_path,
)
interface_name = interface_name_raw.get("INTERFACE_NAME", "")
assert interface_name, "Can not find interface name"
print(f"interface_name: {interface_name}")

# Get the interface ip
err_msg = f"Failed to get interface details for: {interface_name}"
interface_details = node.execute(
cmd=f"ip addr show {interface_name}",
sudo=True,
expected_exit_code=0,
expected_exit_code_failure_message=err_msg,
).stdout
print(f"interface_details: {interface_details}")
ip_regex = re.compile(r"\binet (?P<INTERFACE_IP>\d+\.\d+\.\d+\.\d+)/\d+\b")
interface_ip = find_group_in_lines(
lines=interface_details,
pattern=ip_regex,
single_line=False,
)
passthrough_nic_ip = interface_ip.get("INTERFACE_IP", "")
print(f"passthrough_nic_ip: {passthrough_nic_ip}")
assert passthrough_nic_ip, "Can not find interface IP"

server_node = cast(RemoteNode, node)
server_node.internal_address = self._get_passthrough_nw_ip(node)

host_node = cast(RemoteNode, ctx.host_node)

return server_node, host_node

0 comments on commit 0176cf9

Please sign in to comment.