Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
- virtual_network.migrate.migrate_with_bridge_type_interface:
type = migrate_with_bridge_type_interface
start_vm = "no"
take_regular_screendumps = no
storage_type = 'nfs'
setup_local_nfs = 'yes'
disk_type = "file"
disk_source_protocol = "netfs"
nfs_mount_dir = "/var/lib/libvirt/migrate"
nfs_mount_options = "soft,timeo=50,retrans=3"
virsh_migrate_dest_state = running
virsh_migrate_options = "--live --p2p --verbose"
virsh_migrate_connect_uri = "qemu:///system"
outside_ip = "www.google.com"
migration_setup = "yes"
iface_model = "virtio"
iface_queues = "5"
new_queues = "3"
ssh_remote_auth = True
unprivileged_user = ""
mnt_path_name = ${nfs_mount_dir}
nfs_server_ip = "${migrate_source_host}"
migrate_desturi_port = "22"
migrate_desturi_type = "ssh"
virsh_migrate_desturi = "qemu+ssh://${migrate_dest_host}/system"
vm_ping_outside = "pass"
expected_xpath = [{'element_attrs': ["//interface/driver[@queues='${iface_queues}']"]}]
check_network_accessibility_after_mig = "yes"
variants:
- linux_bridge:
bridge_type = "linux"
iface_type = "bridge"
bridge_name = "br0"
iface_dict = {'source': {'bridge': '${bridge_name}'},'type_name':'${iface_type}', 'model':'${iface_model}', 'driver': {'driver_attr': {'queues': '${iface_queues}'}}}
- ovs_bridge:
bridge_type = "ovs"
ovs_bridge_name = "ovsbr0"
net_name = 'ovs-net'
network_dict = {'bridge': {'name': '${ovs_bridge_name}'}, 'forward': {'mode': 'bridge'}, 'name': '${net_name}', 'virtualport_type': 'openvswitch'}
iface_type = "network"
iface_dict = {'type_name':'${iface_type}', 'source': {'network': '${net_name}'}, 'model':'${iface_model}', 'driver': {'driver_attr': {'queues': '${iface_queues}'}}}
variants:
- precopy_migration:
migration_type = "precopy"
virsh_migrate_extra = ""
migrate_vm_back = "yes"
- postcopy_migration:
migration_type = "postcopy"
virsh_migrate_extra = "--postcopy"
- cancel_migration:
migration_type = "cancel"
cancel_migration = "yes"
status_error = "yes"
err_msg = "operation aborted: migration out job: canceled by client|operation aborted: job 'migration out' canceled by client"
virsh_migrate_extra = "--bandwidth 10"
action_during_mig = '[{"func": "virsh.domjobabort", "after_event": "iteration: '1'", "func_param": "'%s' % params.get('migrate_main_vm')"}]'
variants:
- start_with_interface:
interface_timing = "start"
- hotplug_interface:
interface_timing = "hotplug"
hotplug = "yes"
attach_interface = "yes"
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# Copyright Redhat
#
# SPDX-License-Identifier: GPL-2.0
# Author: Nannan Li<[email protected]>
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
import aexpect.remote
import re
import os

from virttest import utils_net
from virttest import virsh
from virttest.libvirt_xml import vm_xml
from virttest.utils_libvirt import libvirt_network
from virttest.utils_libvirt import libvirt_vmxml
from virttest.libvirt_xml.vm_xml import VMXML

from provider.guest_os_booting import guest_os_booting_base as guest_os
from provider.migration import base_steps
from provider.virtual_network import network_base


def run(test, params, env):
"""
Test migration with bridge type interface
1. Setup bridge and virtual network according to bridge type
2. Migrate to target host
3. Check on target host for network functions:
- Guest ping outside
- Check for multiqueue
- Check multiqueues in VM live XML (should have <driver ... queues='5'>)
4. Migrate back from dst to src

:param test: test object
:param params: Dictionary with the test parameters
:param env: Dictionary with test environment.
"""

def check_multiqueue_in_guest(vm_session):
"""
Check multiqueue configuration inside the guest.

:params, vm_session: vm session object.
"""
test.log.info("Checking multiqueue configuration in guest")
guest_iface_info = vm_session.cmd_output("ip --color=never l").strip()
iface_matches = re.findall(
r"^\d+: (\S+?)[@:].*state UP.*$", guest_iface_info, re.MULTILINE)
if not iface_matches:
test.fail("Failed to get network interface name in guest")
iface_name = iface_matches[0]

_, output = vm_session.cmd_status_output("ethtool -l %s" % iface_name)
test.log.debug("ethtool cmd output:%s" % output)
if not re.findall("Combined:.*?%s" % iface_queues, output):
test.fail("Expected Current hardware settings Combined: %d" % iface_queues)

test.log.info("Setting combined queues to 3 for %s", iface_name)
utils_net.set_channel(vm_session, iface_name, "combined", new_queues)
_, output = vm_session.cmd_status_output("ethtool -l %s" % iface_name)
if not re.findall("Combined:.*?%s" % new_queues, output):
test.fail("Failed to set combined queues: %s" % new_queues)

def setup_vm_interface():
"""
Setup VM interface according to configuration
"""
vm_xml = VMXML.new_from_inactive_dumpxml(vm_name)
vm_xml.remove_all_device_by_type('interface')
vm_xml.sync()
if interface_timing == "hotplug":
if not vm.is_alive():
vm.start()
iface = libvirt_vmxml.create_vm_device_by_type('interface', iface_dict)
virsh.attach_device(vm_name, iface.xml, flagstr="--config",
debug=True, ignore_status=False)
Comment on lines +74 to +78
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Hotplug path never attaches the interface to the running guest

Line 77 currently calls virsh attach-device with only --config, while the VM is already running. That updates the persistent XML but does not hotplug into the live guest, so the hotplug cases run migration without the intended bridge interface. Please pass --live together with --config (or stop the VM before the attach) to ensure the interface is present during migration.

Apply this diff to fix the issue:

-            virsh.attach_device(vm_name, iface.xml, flagstr="--config",
+            virsh.attach_device(vm_name, iface.xml, flagstr="--config --live",
                                 debug=True, ignore_status=False)
📝 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
if not vm.is_alive():
vm.start()
iface = libvirt_vmxml.create_vm_device_by_type('interface', iface_dict)
virsh.attach_device(vm_name, iface.xml, flagstr="--config",
debug=True, ignore_status=False)
if not vm.is_alive():
vm.start()
iface = libvirt_vmxml.create_vm_device_by_type('interface', iface_dict)
virsh.attach_device(vm_name, iface.xml, flagstr="--config --live",
debug=True, ignore_status=False)
🤖 Prompt for AI Agents
In
libvirt/tests/src/virtual_network/migrate/migrate_with_bridge_type_interface.py
around lines 74 to 78, the hotplug path only updates persistent XML by calling
virsh.attach_device with "--config" while the VM is running; change the attach
call to include the live flag (pass "--config --live" as the flagstr) so the
interface is hotplugged into the running guest (alternatively, stop the VM
before attaching), ensuring the interface is present during migration.

else:
libvirt_vmxml.modify_vm_device(
VMXML.new_from_inactive_dumpxml(vm_name), 'interface',
iface_dict)
vm.start()
vm.wait_for_serial_login().close()
test.log.debug("Guest xml:\n%s", VMXML.new_from_dumpxml(vm_name))

def cleanup_nvram():
"""
Clean up NVRAM file to avoid UEFI boot issues
"""
nvram_file = f"/var/lib/libvirt/qemu/nvram/{vm_name}_VARS.fd"
if os.path.exists(nvram_file):
test.log.info(f"Removing NVRAM file: {nvram_file}")
os.remove(nvram_file)

def setup_test():
"""
Setup test environment for migration with bridge type interface
"""
test.log.info("Setting up test environment")
# Clean up NVRAM to avoid UEFI boot issues
cleanup_nvram()

if bridge_type == "linux":
utils_net.create_linux_bridge_tmux(bridge_name, iface_name=host_iface)

remote_host_iface = utils_net.get_default_gateway(
iface_name=True, session=remote_session, force_dhcp=True, json=True)
params.update({"remote_host_iface": remote_host_iface})
utils_net.create_linux_bridge_tmux(
bridge_name, iface_name=remote_host_iface, session=remote_session)

elif bridge_type == "ovs":
utils_net.create_ovs_bridge(ovs_bridge_name, ip_options='-color=never')

utils_net.create_ovs_bridge(ovs_bridge_name, session=remote_session,
ip_options='-color=never')
libvirt_network.create_or_del_network(network_dict, remote_args=remote_virsh_dargs)
libvirt_network.create_or_del_network(network_dict)

if vm.is_alive():
vm.destroy()

setup_vm_interface()

def run_test():
"""
Run the main test: migration and verification
"""
test.log.info("TEST_STEP: Migrating VM to target host")
migration_obj.setup_connection()
if not vm.is_alive():
vm.start()
migration_obj.run_migration()

if not cancel_migration:
test.log.info("TEST_STEP: Checking VM network connectivity on target host")
backup_uri, vm.connect_uri = vm.connect_uri, dest_uri
if vm.serial_console is not None:
vm.cleanup_serial_console()
vm.create_serial_console()
vm_session_after_mig = vm.wait_for_serial_login(timeout=240)
vm_session_after_mig.cmd("dhclient -r; dhclient", timeout=120)

test.log.info("TEST_STEP: Testing guest ping to outside")
ips = {'outside_ip': outside_ip}
network_base.ping_check(params, ips, vm_session_after_mig)

test.log.info("TEST_STEP: Checking multiqueue")
check_multiqueue_in_guest(vm_session_after_mig)
virsh_obj = virsh.VirshPersistent(uri=dest_uri)
libvirt_vmxml.check_guest_xml_by_xpaths(
VMXML.new_from_dumpxml(vm_name, virsh_instance=virsh_obj),
expected_xpath)

if migrate_vm_back:
test.log.info("TEST_STEP: Migrating VM back to source host")
migration_obj.run_migration_back()

def teardown_test():
"""
Cleanup test environment
"""
test.log.info("Cleaning up test environment")
if bridge_type == "linux":
utils_net.delete_linux_bridge_tmux(bridge_name, iface_name=host_iface)
utils_net.delete_linux_bridge_tmux(
bridge_name, iface_name=params.get("remote_host_iface"),
session=remote_session)

elif bridge_type == "ovs":
utils_net.delete_ovs_bridge(ovs_bridge_name, ip_options='-color=never')
utils_net.delete_ovs_bridge(ovs_bridge_name, session=remote_session, ip_options='-color=never')

libvirt_network.create_or_del_network(network_dict, is_del=True, remote_args=remote_virsh_dargs)
libvirt_network.create_or_del_network(network_dict, is_del=True)
migration_obj.cleanup_connection()

server_ip = params["server_ip"] = params.get("migrate_dest_host")
server_user = params.get("server_user")
server_pwd = params.get("server_pwd") or params.get("migrate_dest_pwd")
outside_ip = params.get("outside_ip")
bridge_name = params.get("bridge_name")
bridge_type = params.get("bridge_type")
ovs_bridge_name = params.get("ovs_bridge_name")
network_dict = eval(params.get("network_dict", "{}"))
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Replace eval() with ast.literal_eval() for safer parsing.

Using eval() is a security risk as it can execute arbitrary Python code. Use ast.literal_eval() instead, which safely evaluates literal expressions.

First, add the import at the top of the file:

import ast

Then apply these fixes:

-    network_dict = eval(params.get("network_dict", "{}"))
+    network_dict = ast.literal_eval(params.get("network_dict", "{}"))
-    expected_xpath = eval(params.get("expected_xpath"))
+    expected_xpath = ast.literal_eval(params.get("expected_xpath"))
-    iface_dict = eval(params.get("iface_dict", "{}"))
+    iface_dict = ast.literal_eval(params.get("iface_dict", "{}"))

Also applies to: 205-206

🧰 Tools
🪛 Ruff (0.13.1)

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

(S307)

🤖 Prompt for AI Agents
In
libvirt/tests/src/virtual_network/migrate/migrate_with_bridge_type_interface.py
around line 192 (and also lines 205-206), replace the insecure use of
eval(params.get("network_dict", "{}")) with ast.literal_eval for safe parsing;
add "import ast" at the top of the file, and change the two other eval usages on
lines 205-206 to ast.literal_eval as well, ensuring you handle potential
ValueError/SyntaxError from literal_eval if needed (e.g., wrap in try/except or
provide a safe default).

interface_timing = params.get("interface_timing")
iface_queues = int(params.get("iface_queues", "5"))
new_queues = int(params.get("new_queues", "3"))
migrate_vm_back = params.get_boolean("migrate_vm_back")
cancel_migration = params.get_boolean("cancel_migration")

remote_virsh_dargs = {'remote_ip': server_ip, 'remote_user': server_user,
'remote_pwd': server_pwd, 'unprivileged_user': params.get("unprivileged_user"),
'ssh_remote_auth': params.get("ssh_remote_auth")}

src_uri = params.get("virsh_migrate_connect_uri")
dest_uri = params.get("virsh_migrate_desturi")
expected_xpath = eval(params.get("expected_xpath"))
iface_dict = eval(params.get("iface_dict", "{}"))
host_iface = params.get('host_iface')
host_iface = host_iface if host_iface else utils_net.get_default_gateway(
iface_name=True, force_dhcp=True, json=True)

vm_name = guest_os.get_vm(params)
vm = env.get_vm(vm_name)
new_xml = vm_xml.VMXML.new_from_inactive_dumpxml(vm_name)

remote_session = aexpect.remote.remote_login("ssh", server_ip, "22",
server_user, server_pwd,
r'[$#%]')
migration_obj = base_steps.MigrationBase(test, vm, params)

try:
setup_test()
run_test()
finally:
teardown_test()
if remote_session:
remote_session.close()