Skip to content
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
45 changes: 45 additions & 0 deletions libvirt/tests/cfg/virtual_network/qemu/pktgen_perf_test.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
- virtual_network.qemu_test.pktgen_perf_test:
type = pktgen_burst_mode_test
no Windows
only virtio_net
no Host_RHEL.m5, Host_RHEL.m6
vm_ping_host = "pass"
pkg_dir = "tx rx"
pktgen_script = "pktgen_sample01_simple pktgen_sample03_burst_single_flow pktgen_sample05_flow_per_thread"
pktgen_threads = 1
pkt_size = 64
burst = 1
pktgen_test_timeout = 30
record_list = pkt_size run_threads burst mpps
guest_ver_cmd = "uname -r"
kvm_ver_chk_cmd = "rpm -qa qemu-kvm-rhev qemu-kvm"
test_vm = no
flush_firewall_rules_cmd = "iptables -F ; nft flush ruleset"
queues_nic1 = "8"
create_vm_libvirt = "yes"
kill_vm_libvirt = "yes"
ovmf:
kill_vm_libvirt_options = " --nvram"
master_images_clone = img1
remove_image_image1 = yes

variants:
- @default:
- vhost_vdpa:
# Run pktgen perf test with vhost-vdpa interface.
# Default intent is VM <-> external host traffic testing.
dev_type = "vdpa"
driver_queues = 9
vm_iface_driver = "virtio_net"
mac_addr = "52:54:00:00:00:00"
iface_dict = {'model': 'virtio', 'source': {'dev': '/dev/vhost-vdpa-0'}, 'type_name': 'vdpa', 'driver': {'driver_attr': {'queues': '${driver_queues}'}}, 'mac_address': mac_addr}
vm_attrs = {'vcpu': 10, 'current_vcpu': 10}
# Please modify the commented-out example values below
# client = ip
# username_client = your_username
# password_client = your_password
# shell_port_client = 22
# shell_client_client = ssh
# shell_prompt_client = [$#%]
# client_pktgen_iface = ethX # external host iface used by pktgen
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Parameter name mismatch with pktgen_utils.py.

The config defines client_pktgen_iface but pktgen_utils.py line 66 retrieves it as pktgen_rx_iface. This mismatch will cause the interface to be None when running rx tests with a client.

Suggested fix

Either rename here to match the code:

-            # client_pktgen_iface = ethX       # external host iface used by pktgen
+            # pktgen_rx_iface = ethX           # external host iface used by pktgen

Or update pktgen_utils.py line 66:

-                self.interface = params.get("pktgen_rx_iface")
+                self.interface = params.get("client_pktgen_iface")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# client_pktgen_iface = ethX # external host iface used by pktgen
# pktgen_rx_iface = ethX # external host iface used by pktgen
🤖 Prompt for AI Agents
In `@libvirt/tests/cfg/virtual_network/qemu/pktgen_perf_test.cfg` at line 44, The
config parameter name client_pktgen_iface does not match the key read in
pktgen_utils.py (pktgen_rx_iface), causing the iface to be None; fix by making
the names consistent: either rename the config entry to pktgen_rx_iface or
change the lookup in pktgen_utils.py (where pktgen_rx_iface is fetched around
line 66) to read client_pktgen_iface (and update any other references to
pktgen_rx_iface in that module to use the chosen name) so the RX test receives
the correct interface value.

# pktgen_tx_dst_mac = xx:xx:xx:xx:xx:xx
42 changes: 40 additions & 2 deletions libvirt/tests/src/virtual_network/qemu/pktgen_burst_mode_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@
from avocado.utils import distro
from avocado.utils import process

from virttest import virsh
from virttest import utils_misc
from virttest import utils_net
from virttest.libvirt_xml import vm_xml
from virttest.utils_libvirt import libvirt_vmxml
from virttest.utils_test import libvirt

from provider.virtual_network import pktgen_utils
from provider.virtual_network import network_base
from provider.interface import interface_base


def write_to_result_file(test, params):
Expand Down Expand Up @@ -61,10 +65,36 @@ def run_test():
pktgen_utils.install_package(host_ver)

test.log.debug("TEST_STEP 2: Test with guest and host connectivity")
recreate_serial_console = False
# vhost-vdpa needs XML updates before starting VM
if params.get("dev_type") == "vdpa" and params.get("iface_dict"):
# Ensure VM is stopped before XML changes
virsh.destroy(vm_name, debug=True, ignore_status=True)
recreate_serial_console = True

libvirt_vmxml.remove_vm_devices_by_type(vm, "interface")
vm_attrs = eval(params.get("vm_attrs", "{}"))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use ast.literal_eval instead of eval for security.

eval() can execute arbitrary code if vm_attrs contains malicious input. Since this parses a dictionary literal from configuration, use the safer ast.literal_eval.

Suggested fix

Add the import at the top:

 import os
+import ast

Then replace eval:

-            vm_attrs = eval(params.get("vm_attrs", "{}"))
+            vm_attrs = ast.literal_eval(params.get("vm_attrs", "{}"))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
vm_attrs = eval(params.get("vm_attrs", "{}"))
vm_attrs = ast.literal_eval(params.get("vm_attrs", "{}"))
🧰 Tools
🪛 Ruff (0.14.13)

76-76: Use of possibly insecure function; consider using ast.literal_eval

(S307)

🤖 Prompt for AI Agents
In `@libvirt/tests/src/virtual_network/qemu/pktgen_burst_mode_test.py` at line 76,
Replace the unsafe use of eval when parsing vm_attrs: instead of vm_attrs =
eval(params.get("vm_attrs", "{}")), import the ast module and call
ast.literal_eval on the params.get(...) value to safely parse the dictionary
literal; update the code path around vm_attrs and ensure any default "{}" is
passed through ast.literal_eval as needed.

if vm_attrs:
vmxml = vm_xml.VMXML.new_from_dumpxml(vm_name)
vmxml.setup_attrs(**vm_attrs)
vmxml.sync()

# Add a single vhost-vdpa interface
vmxml = vm_xml.VMXML.new_from_dumpxml(vm_name)
iface_dict = interface_base.parse_iface_dict(params)
iface_dev = interface_base.create_iface(params.get("dev_type"), iface_dict)
libvirt.add_vm_device(vmxml, iface_dev)

if vm.virtnet:
vm.virtnet[0].mac = params.get("mac_addr")
vm.virtnet[0].nettype = "vdpa"

if not vm.is_alive():
vm.start()
test.log.debug("Test with Guest xml:%s", vm_xml.VMXML.new_from_dumpxml(vm_name))
session = vm.wait_for_serial_login(restart_network=True)
session = vm.wait_for_serial_login(
restart_network=True, recreate_serial_console=recreate_serial_console
)
network_base.ping_check(params, ips, session, force_ipv4=True)

test.log.debug("TEST_STEP 3: Install package and Run pktgen in burst mode.")
Expand All @@ -86,7 +116,15 @@ def run_test():
params, result_file, test_vm, vm, session)

test.log.debug("TEST_STEP: Check the guest dmesg without error messages.")
vm.verify_dmesg()
if params.get("dev_type") == "vdpa":
# vhost-vdpa may not have a guest IP (ARP/arping), so vm.verify_dmesg()
# can fail by attempting a network login.
utils_misc.verify_dmesg(
ignore_result=False,
session=session,
)
else:
vm.verify_dmesg()
vm.verify_kernel_crash()

result_file.close()
Expand Down
82 changes: 62 additions & 20 deletions provider/virtual_network/pktgen_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from avocado.utils import process

from virttest import data_dir
from virttest import remote
from virttest import utils_net
from virttest import utils_misc
from virttest import utils_package
Expand All @@ -25,26 +26,56 @@ def configure_pktgen(
pkt_cate,
vm=None,
session_serial=None,
script=None,
params=None
):
"""
Configure pktgen test environment for different packet categories.

:param pkt_cate: Packet category (tx, rx, loopback)
:param vm: VM instance
:param session_serial: Serial session for guest command execution
:param params: Dictionary with the test parameters
:param script: Script name to execute
:return: Configured PktgenConfig instance
"""
source_path = os.path.join(data_dir.get_shared_dir(), "scripts/pktgen_perf")
params = params or {}
guest_mac = vm.get_mac_address(0)
guest_eth = utils_net.get_linux_ifname(session_serial, guest_mac)

Comment on lines +43 to 46
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing null checks for vm and session_serial.

guest_mac and guest_eth are computed unconditionally, but vm and session_serial have default values of None. If either is None, this will raise an AttributeError.

Suggested fix

Add validation at the start:

         params = params or {}
+        if vm is None or session_serial is None:
+            raise ValueError("vm and session_serial are required for configure_pktgen")
         guest_mac = vm.get_mac_address(0)
         guest_eth = utils_net.get_linux_ifname(session_serial, guest_mac)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
params = params or {}
guest_mac = vm.get_mac_address(0)
guest_eth = utils_net.get_linux_ifname(session_serial, guest_mac)
params = params or {}
if vm is None or session_serial is None:
raise ValueError("vm and session_serial are required for configure_pktgen")
guest_mac = vm.get_mac_address(0)
guest_eth = utils_net.get_linux_ifname(session_serial, guest_mac)
🤖 Prompt for AI Agents
In `@provider/virtual_network/pktgen_utils.py` around lines 43 - 46, Add
null/validation checks before using vm and session_serial in pktgen_utils.py:
ensure vm is not None and has get_mac_address (used to set guest_mac) and ensure
session_serial is not None before calling utils_net.get_linux_ifname (used to
set guest_eth); if either is missing, raise a clear ValueError or return early
with a descriptive error message. Update the block that currently calls
vm.get_mac_address(0) and utils_net.get_linux_ifname(session_serial, guest_mac)
to guard those calls and include the function/variable names (get_mac_address,
get_linux_ifname, guest_mac, guest_eth) in the error messages so the failure
point is obvious.

if pkt_cate == "tx":
LOG_JOB.info("test guest tx pps performance")
vm.copy_files_to(source_path, self.dest_path)
guest_mac = vm.get_mac_address(0)
self.interface = utils_net.get_linux_ifname(session_serial, guest_mac)
host_iface = libvirt.get_ifname_host(vm.name, guest_mac)
dsc_dev = utils_net.Interface(host_iface)
self.dsc = dsc_dev.get_mac()
if params.get("pktgen_tx_dst_mac"):
self.dsc = params.get("pktgen_tx_dst_mac")
else:
host_iface = libvirt.get_ifname_host(vm.name, guest_mac)
dsc_dev = utils_net.Interface(host_iface)
self.dsc = dsc_dev.get_mac()
self.interface = guest_eth
self.runner = session_serial.cmd
elif pkt_cate == "rx":
LOG_JOB.info("test guest rx pps performance")
if params.get("client"):
client_ip = params.get("client")
username = params.get("username_client", "root")
password = params.get("password_client")
remote.copy_files_to(
client_ip, "scp", username, password, 22,
source_path, self.dest_path, timeout=600
)
self.interface = params.get("pktgen_rx_iface")
remote_session = remote.remote_login(
"ssh", client_ip, "22",
username, password, r'[$#%]'
)
self.runner = remote_session.cmd
else:
process.run("cp -r %s %s" % (source_path, self.dest_path))
self.interface = libvirt.get_ifname_host(vm.name, guest_mac)
self.runner = process.run
self.dsc = guest_mac
return self

def generate_pktgen_cmd(
Expand Down Expand Up @@ -82,44 +113,48 @@ def generate_pktgen_cmd(

if (
session_serial
and self.runner.__name__ == session_serial.cmd.__name__
and self.runner == session_serial.cmd
):
cmd = f"{cmd} &"

return cmd


def run_test(script, cmd, runner, interface, timeout):
def run_test(script, cmd, runner, interface, timeout, session_serial=None, pkt_cate="tx"):
"""
Run pktgen script on remote and gather packet numbers/time and
calculate mpps.
:param script: pktgen script name.
:param cmd: The command to execute the pktgen script
:param runner: The command runner function
:param interface: The network interface used by pktgen.
:param interface: The VM Ethernet interface used to collect packet counters.
:param timeout: The maximum time allowed for the test to run
:param session_serial: Session serial for VM
:param pkt_cate: Packet category (tx/rx), used to select counter type
:return: The calculated MPPS (Million Packets Per Second)
"""

packets = "cat /sys/class/net/%s/statistics/tx_packets" % interface
counter = "rx_packets" if pkt_cate == "rx" else "tx_packets"
packets = "cat /sys/class/net/%s/statistics/%s" % (interface, counter)
LOG_JOB.info("Start pktgen test by cmd '%s'", cmd)
try:
packet_b = runner(packets)
packet_b = session_serial.cmd(packets)
packet_a = None
runner(cmd, timeout)
packet_a = session_serial.cmd(packets)
except aexpect.ShellTimeoutError:
# when pktgen script is running on guest, the pktgen process
# need to be killed.
kill_cmd = (
"kill -9 `ps -ef | grep %s --color | grep -v grep | "
"awk '{print $2}'`" % script
)
runner(kill_cmd)
packet_a = runner(packets)
if session_serial and runner == session_serial.cmd:
kill_cmd = (
"kill -9 `ps -ef | grep %s --color | grep -v grep | "
"awk '{print $2}'`" % script
)
runner(kill_cmd)
packet_a = session_serial.cmd(packets)
except process.CmdError:
# when pktgen script is running on host, the pktgen process
# will be quit when timeout triggers, so no need to kill it.
packet_a = runner(packets)
packet_a = session_serial.cmd(packets)
Comment on lines +123 to +157
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

session_serial used unconditionally but has None default.

Lines 136, 139, 149, and 153 all call session_serial.cmd(packets), but session_serial defaults to None. This will raise AttributeError if the function is called without a session.

Either make session_serial required or add None checks.

Suggested fix - make parameter required
-def run_test(script, cmd, runner, interface, timeout, session_serial=None, pkt_cate="tx"):
+def run_test(script, cmd, runner, interface, timeout, session_serial, pkt_cate="tx"):
🤖 Prompt for AI Agents
In `@provider/virtual_network/pktgen_utils.py` around lines 119 - 153, The
run_test function uses session_serial unconditionally (calls
session_serial.cmd(packets)) while it defaults to None; update run_test to
handle a missing session_serial: either make session_serial a required argument
in the function signature or add guards where session_serial.cmd(packets) is
called—use runner(packets) when session_serial is None (or raise a clear
ValueError at the start if you prefer requiring it). Specifically, modify the
calls that reference session_serial.cmd (the initial packet_b read, the packet_a
reads in the try and both except blocks) to check if session_serial is not None
before calling session_serial.cmd, and fall back to runner(packets) or raise a
descriptive error; keep the existing kill logic that checks session_serial and
runner equality intact.


return "{:.2f}".format((int(packet_a) - int(packet_b)) / timeout / 10 ** 6)

Expand Down Expand Up @@ -193,6 +228,8 @@ def run_tests_for_category(
# Get single values for threads and burst instead of looping
threads = params.get("pktgen_threads", "")
burst = params.get("burst", "")
guest_mac = vm.get_mac_address(0)
guest_eth = utils_net.get_linux_ifname(session_serial, guest_mac)

record_line = ""
for record in record_list:
Expand All @@ -210,7 +247,7 @@ def run_tests_for_category(
size = params.get("pkt_size", "")
if pkt_cate != "loopback":
pktgen_config = pktgen_config.configure_pktgen(
pkt_cate, vm, session_serial
pkt_cate, vm, session_serial, script=script, params=params
)
exec_cmd = pktgen_config.generate_pktgen_cmd(
script,
Expand All @@ -222,14 +259,19 @@ def run_tests_for_category(
burst,
session_serial,
)
pkt_cate_r = None
if exec_cmd:
pkt_cate_r = run_test(
script,
exec_cmd,
pktgen_config.runner,
pktgen_config.interface,
guest_eth,
timeout,
session_serial,
pkt_cate=pkt_cate,
)
else:
pkt_cate_r = None

line = "%s|" % format_result(size)
line += "%s|" % format_result(threads)
Expand Down