Skip to content

Commit 43cf889

Browse files
committed
migration: Add destination VM backup and cleanup support
Add optional destination VM backup/cleanup functionality to MigrationBase: - New parameter 'do_destination_vm_backup' (default: no) to enable feature - Accept optional remote_virsh session for destination host operations - Check if VM exists on destination before migration - Fail test if destination VM is running (blocker condition) - Backup and undefine pre-existing stopped VMs to avoid conflicts - Use comprehensive undefine options (--nvram, --managed-save, etc.) - Restore destination VM in cleanup_default() Update migration_iommu_device test: - Add setup_remote_virsh_session() helper function - Create single remote virsh session shared across test operations - Refactor check_iommu_xml() to reuse remote session (no duplication) - Add cleanup_default() call in finally block - Properly close remote session in finally block This allows migration tests to run on non-clean environments without depending on destination host state. AI assisted code and commit. Human reviewed. Signed-off-by: hholoubk <hholoubk@redhat.com> Refactor MigrationBase to cache destination host credentials and reuse them across methods, and extract destination VM backup/undefine flow into a helper method. When do_destination_vm_backup is enabled, include destination host info in logs, fail with clearer message if the destination VM is running, and restore a backed-up destination VM by copying its XML file to the destination host before defining it via remote virsh. When destination VM backup is enabled, return early if the VM does not exist on the destination host, and enable debug logging for the existence/running-state checks. Only call destination VM backup/undefine helper when do_destination_vm_backup is enabled, and drop the redundant guard from the helper. AI assisted code and commit. Human reviewed. Signed-off-by: hholoubk <hholoubk@redhat.com> migration: Fix remote_virsh session resource leak in migration_iommu_device Move remote_virsh session creation and migration_obj initialization inside the try block to prevent resource leaks if MigrationBase.__init__ fails. Also remove redundant cleanup_default() call since it's already called within cleanup_connection(). Changes: - Initialize remote_virsh and migration_obj to None before try block - Move session creation inside try block - Add null checks before cleanup operations - Remove duplicate cleanup_default() call AI assisted code and commit. Human reviewed. Signed-off-by: hholoubk <hholoubk@redhat.com> Co-authored-by: Cursor <cursoragent@cursor.com> migration: Check destination host libvirt version for checkpoints-metadata Replace local version_compare() call with is_libvirt_feature_supported() using a remote SSH session to the destination host. The previous code incorrectly checked the local libvirt version when deciding whether to pass --checkpoints-metadata to undefine on the remote host. AI assisted code and commit. Human reviewed. Signed-off-by: hholoubk <hholoubk@redhat.com> Made-with: Cursor dd
1 parent 6011393 commit 43cf889

3 files changed

Lines changed: 129 additions & 42 deletions

File tree

libvirt/tests/cfg/sriov/vIOMMU/migration_iommu_device.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
virsh_migrate_connect_uri = "qemu:///system"
2626
check_network_accessibility_after_mig = "yes"
2727
check_cont_ping = "yes"
28+
do_destination_vm_backup = "yes"
2829
disk_driver = {'name': 'qemu', 'type': 'qcow2', 'iommu': 'on'}
2930
variants:
3031
- virtio:

libvirt/tests/src/sriov/vIOMMU/migration_iommu_device.py

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,39 @@ def run(test, params, env):
1313
This case starts vm with different iommu device settings then migrate it
1414
to and back to check network works well.
1515
"""
16-
def check_iommu_xml(vm, params):
16+
def setup_remote_virsh_session(params):
17+
"""
18+
Setup remote virsh session for destination host operations
19+
20+
:param params: Dictionary with the test parameters
21+
:return: VirshPersistent session
22+
"""
23+
server_ip = params.get("server_ip")
24+
server_user = params.get("server_user", "root")
25+
server_pwd = params.get("server_pwd")
26+
27+
remote_virsh_dargs = {
28+
'remote_ip': server_ip,
29+
'remote_user': server_user,
30+
'remote_pwd': server_pwd,
31+
'unprivileged_user': None,
32+
'ssh_remote_auth': True
33+
}
34+
return virsh.VirshPersistent(**remote_virsh_dargs)
35+
36+
def check_iommu_xml(vm, params, remote_virsh):
1737
"""
1838
Check the iommu xml of the migrated vm
1939
2040
:param vm: VM object
2141
:param params: Dictionary with the test parameters
42+
:param remote_virsh: VirshPersistent session for destination host
2243
"""
2344
iommu_dict = eval(params.get('iommu_dict', '{}'))
2445
if not iommu_dict:
2546
return
26-
server_ip = params.get("server_ip")
27-
server_user = params.get("server_user", "root")
28-
server_pwd = params.get("server_pwd")
29-
remote_virsh_dargs = {'remote_ip': server_ip,
30-
'remote_user': server_user,
31-
'remote_pwd': server_pwd,
32-
'unprivileged_user': None,
33-
'ssh_remote_auth': True}
34-
virsh_session_remote = virsh.VirshPersistent(**remote_virsh_dargs)
3547
migrated_vmxml = vm_xml.VMXML.new_from_inactive_dumpxml(
36-
vm.name, virsh_instance=virsh_session_remote)
48+
vm.name, virsh_instance=remote_virsh)
3749
vm_iommu = migrated_vmxml.devices.by_device_tag(
3850
'iommu')[0].fetch_attrs()
3951
for attr, value in iommu_dict.items():
@@ -71,14 +83,18 @@ def setup_test():
7183
vm_name = params.get("main_vm", "avocado-vt-vm1")
7284
vm = env.get_vm(vm_name)
7385
test_obj = viommu_base.VIOMMUTest(vm, test, params)
74-
migration_obj = base_steps.MigrationBase(test, vm, params)
7586

87+
remote_virsh = None
88+
migration_obj = None
7689
try:
90+
# Setup remote virsh session for destination operations
91+
remote_virsh = setup_remote_virsh_session(params)
92+
migration_obj = base_steps.MigrationBase(test, vm, params, remote_virsh=remote_virsh)
7793
setup_test()
7894
test.log.info("TEST_STEP: Migrate the VM to the target host.")
7995
migration_obj.run_migration()
8096
migration_obj.verify_default()
81-
check_iommu_xml(vm, params)
97+
check_iommu_xml(vm, params, remote_virsh)
8298

8399
migrate_vm_back = "yes" == params.get("migrate_vm_back", "yes")
84100
if migrate_vm_back:
@@ -87,5 +103,8 @@ def setup_test():
87103
migration_obj.migration_test.ping_vm(vm, params)
88104
migration_obj.check_vm_cont_ping(False)
89105
finally:
90-
migration_obj.cleanup_connection()
106+
if migration_obj:
107+
migration_obj.cleanup_connection()
108+
if remote_virsh:
109+
remote_virsh.close_session()
91110
test_obj.teardown_iommu_test()

provider/migration/base_steps.py

Lines changed: 95 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from virttest import migration
99
from virttest import libvirt_remote
1010
from virttest import libvirt_vm
11+
from virttest import libvirt_version
1112
from virttest import remote
1213
from virttest import utils_config
1314
from virttest import utils_conn
@@ -38,10 +39,12 @@ class MigrationBase(object):
3839
:param remote_libvirtd_log: remote.RemoteFile object
3940
"""
4041

41-
def __init__(self, test, vm, params):
42+
def __init__(self, test, vm, params, remote_virsh=None):
4243
"""
4344
Init params and other necessary variables
4445
46+
:param remote_virsh: Optional VirshPersistent session for destination host operations
47+
4548
"""
4649
self.test = test
4750
self.vm = vm
@@ -53,6 +56,12 @@ def __init__(self, test, vm, params):
5356
self.check_cont_ping = "yes" == self.params.get("check_cont_ping", "no")
5457
self.check_cont_ping_log = self.params.get("check_cont_ping_log", "/tmp/log_file")
5558
self.remote_libvirtd_log = None
59+
self.server_ip = self.params.get("server_ip")
60+
self.server_user = self.params.get("server_user", "root")
61+
self.server_pwd = self.params.get("server_pwd")
62+
self.do_destination_vm_backup = "yes" == self.params.get("do_destination_vm_backup", "no")
63+
self.dest_backup_xml = None
64+
self.remote_virsh = remote_virsh
5665

5766
migration_test = migration.MigrationTest()
5867
migration_test.check_parameters(params)
@@ -63,6 +72,55 @@ def __init__(self, test, vm, params):
6372
new_xml = vm_xml.VMXML.new_from_inactive_dumpxml(vm_name)
6473
self.orig_config_xml = new_xml.copy()
6574

75+
if self.do_destination_vm_backup:
76+
self._backup_and_undefine_destination_vm_if_needed(vm_name)
77+
78+
def _backup_and_undefine_destination_vm_if_needed(self, vm_name):
79+
"""
80+
If enabled, handle pre-existing destination VM with the same name:
81+
- Fail if it is running (blocker)
82+
- Backup its XML (optional / enabled via params)
83+
- Undefine it on destination to avoid name conflict during migration
84+
85+
:param vm_name: VM name to check on destination host
86+
"""
87+
if self.remote_virsh is None:
88+
self.test.fail("Test setup error: Destination VM backup is enabled "
89+
"(do_destination_vm_backup=yes), but no remote_virsh "
90+
"session was provided to MigrationBase.__init__")
91+
92+
if not self.remote_virsh.domain_exists(vm_name, debug=True):
93+
return
94+
95+
# Check if VM is running - we can only backup/undefine if it's not running
96+
if not self.remote_virsh.is_dead(vm_name, debug=True):
97+
self.test.fail(f"VM '{vm_name}' is running on destination host. {self.server_ip} "
98+
"Cannot proceed - VM must be stopped first.")
99+
100+
self.test.log.info(f"VM '{vm_name}' exists on destination host {self.server_ip}, "
101+
"backing it up")
102+
dest_xml = vm_xml.VMXML.new_from_inactive_dumpxml(
103+
vm_name,
104+
virsh_instance=self.remote_virsh
105+
)
106+
self.dest_backup_xml = dest_xml.copy()
107+
108+
# Undefine the VM on destination to avoid conflicts during migration
109+
self.test.log.info(f"Undefining VM '{vm_name}' on destination host {self.server_ip}")
110+
undef_opts = "--managed-save --snapshots-metadata --nvram"
111+
remote_session = remote.wait_for_login('ssh', self.server_ip, '22',
112+
self.server_user, self.server_pwd,
113+
r"[\#\$]\s*$")
114+
try:
115+
checkpoints_params = {"func_supported_since_libvirt_ver": "(7, 5, 0)"}
116+
if libvirt_version.is_libvirt_feature_supported(
117+
checkpoints_params, ignore_error=True, session=remote_session):
118+
undef_opts += " --checkpoints-metadata"
119+
finally:
120+
remote_session.close()
121+
122+
self.remote_virsh.undefine(vm_name, options=undef_opts)
123+
66124
def setup_default(self, use_console=False):
67125
"""
68126
Setup steps by default
@@ -253,19 +311,16 @@ def run_migration_back(self):
253311
options = self.params.get("virsh_migrate_options", "--live --verbose")
254312
dest_uri = self.params.get("virsh_migrate_desturi")
255313
self.vm.connect_uri = dest_uri
256-
server_ip = self.params.get("server_ip")
257-
server_user = self.params.get("server_user", "root")
258-
server_pwd = self.params.get("server_pwd")
259314

260315
client_ip = self.params.get("client_ip")
261316
client_pwd = self.params.get("client_pwd")
262-
runner_on_target = remote.RemoteRunner(host=server_ip,
263-
username=server_user,
264-
password=server_pwd)
317+
runner_on_target = remote.RemoteRunner(host=self.server_ip,
318+
username=self.server_user,
319+
password=self.server_pwd)
265320
ssh_connection = utils_conn.SSHConnection(server_ip=client_ip,
266321
server_pwd=client_pwd,
267-
client_ip=server_ip,
268-
client_pwd=server_pwd)
322+
client_ip=self.server_ip,
323+
client_pwd=self.server_pwd)
269324
try:
270325
ssh_connection.conn_check()
271326
except utils_conn.ConnectionError:
@@ -343,6 +398,27 @@ def cleanup_default(self):
343398
# Clean VM on destination and source
344399
self.migration_test.cleanup_vm(self.vm, dest_uri)
345400
self.orig_config_xml.sync()
401+
402+
# Restore destination VM if it was backed up
403+
if self.dest_backup_xml:
404+
vm_name = self.params.get("migrate_main_vm")
405+
self.test.log.info(f"Restoring VM '{vm_name}' on destination host {self.server_ip}")
406+
remote.scp_to_remote(
407+
self.server_ip,
408+
"22",
409+
self.server_user,
410+
self.server_pwd,
411+
self.dest_backup_xml.xml,
412+
self.dest_backup_xml.xml,
413+
limit="",
414+
log_filename=None,
415+
timeout=60,
416+
interface=None,
417+
)
418+
if not self.dest_backup_xml.define(virsh_instance=self.remote_virsh):
419+
self.test.log.warning(f"Failed to restore VM '{vm_name}' on destination host {self.server_ip}")
420+
421+
self.orig_config_xml.sync()
346422

347423
def setup_connection(self, use_console=False):
348424
"""
@@ -390,9 +466,6 @@ def set_remote_log(self):
390466
log_file = self.params.get("libvirtd_debug_file", "/var/log/libvirt/virtqemud.log")
391467
log_filters = self.params.get("libvirtd_debug_filters", "1:*")
392468
remote_file_type = self.params.get("remote_file_type", "virtqemud")
393-
server_ip = self.params.get("server_ip", self.params.get("migrate_dest_host"))
394-
server_user = self.params.get("server_user", "root")
395-
server_pwd = self.params.get("server_pwd", self.params.get("migrate_dest_pwd"))
396469

397470
self.test.log.debug(f"Start setting {remote_file_type} log on remote host")
398471
service_name = utils_libvirtd.Libvirtd(remote_file_type).service_name
@@ -404,9 +477,9 @@ def set_remote_log(self):
404477
'".*log_filters\s*=.*": \'log_filters="%s"\', '
405478
'".*log_outputs\s*=.*": \'log_outputs="1:file:%s"\'}') % (log_level, log_filters, log_file)
406479
self.remote_libvirtd_log = libvirt_remote.update_remote_file(self.params, libvirtd_conf_dest, file_path)
407-
remote_runner = remote.RemoteRunner(host=server_ip,
408-
username=server_user,
409-
password=server_pwd)
480+
remote_runner = remote.RemoteRunner(host=self.server_ip,
481+
username=self.server_user,
482+
password=self.server_pwd)
410483
utils_libvirtd.Libvirtd(remote_file_type, session=remote_runner.session).restart()
411484
self.params.update({
412485
"remote_session": remote_runner.session
@@ -439,12 +512,9 @@ def check_local_and_remote_log(self, local_str_in_log=True, remote_str_in_log=Tr
439512
local_str_in_log = False
440513
libvirt.check_logfile(check_log, log_file, str_in_log=local_str_in_log)
441514
if check_str_remote_log or check_no_str_remote_log:
442-
server_ip = self.params.get("server_ip")
443-
server_user = self.params.get("server_user", "root")
444-
server_pwd = self.params.get("server_pwd")
445-
runner_on_target = remote.RemoteRunner(host=server_ip,
446-
username=server_user,
447-
password=server_pwd)
515+
runner_on_target = remote.RemoteRunner(host=self.server_ip,
516+
username=self.server_user,
517+
password=self.server_pwd)
448518

449519
if check_str_remote_log:
450520
for check_log in check_str_remote_log:
@@ -469,11 +539,8 @@ def remote_add_or_remove_port(self, port, add=True):
469539
:param port: port
470540
:param add: True for add port, False for remove port
471541
"""
472-
server_ip = self.params.get("server_ip")
473-
server_user = self.params.get("server_user")
474-
server_pwd = self.params.get("server_pwd")
475-
remote_session = remote.wait_for_login('ssh', server_ip, '22',
476-
server_user, server_pwd,
542+
remote_session = remote.wait_for_login('ssh', self.server_ip, '22',
543+
self.server_user, self.server_pwd,
477544
r"[\#\$]\s*$")
478545
firewall_cmd = utils_iptables.Firewall_cmd(remote_session)
479546
if add:
@@ -520,7 +587,7 @@ def prepare_disks_remote(params, vm):
520587
:param vm: vm object
521588
"""
522589
server_ip = params.get("server_ip")
523-
server_user = params.get("server_user")
590+
server_user = params.get("server_user", "root")
524591
server_pwd = params.get("server_pwd")
525592

526593
remote_session = remote.remote_login("ssh", server_ip, "22",
@@ -587,7 +654,7 @@ def check_cpu_for_mig(params):
587654
"""
588655
dest_uri = params.get("virsh_migrate_desturi")
589656
remote_ip = params.get("server_ip")
590-
remote_user = params.get("server_user")
657+
remote_user = params.get("server_user", "root")
591658
remote_pwd = params.get("server_pwd")
592659

593660
# aarch64 only supports migration tests between hosts with the same CPU, so

0 commit comments

Comments
 (0)