mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-11 12:46:49 +00:00
fix(migrate): recovery removes the disk copies of a migration whose worker stopped
A worker that stopped during the copy left the partial files on the destination: libvirt ends the migration and the VM stays, but the files took the space and refused the next try, as a copy never replaces a file. The job records the copies before libvirt makes them, and recovery deletes them while the destination has no VM of it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kk4h2TcUiaWmU5MUszYSea
This commit is contained in:
parent
d2a637cf33
commit
aadf5270f2
5 changed files with 69 additions and 7 deletions
|
|
@ -116,7 +116,7 @@ def _rate(before, after, seconds, scale=1):
|
|||
|
||||
class wvmInstances(wvmConnect):
|
||||
def moveto(self, conn, name, live, unsafe, offline, autoconverge=False, compress=False, uri=None,
|
||||
pools=None):
|
||||
pools=None, on_copy=None):
|
||||
"""Migrates the VM of conn (a wvmInstance on the source) here.
|
||||
|
||||
The mode follows the VM's state, read once: a running VM migrates
|
||||
|
|
@ -128,9 +128,10 @@ class wvmInstances(wvmConnect):
|
|||
(tcp://address) on the destination; None lets libvirt use the
|
||||
destination's own hostname. pools: {target: pool here} of the disks
|
||||
libvirt copies into those pools during the migration (disk_copies);
|
||||
the other disks must be on storage both hosts share. Returns the mode
|
||||
and options used, for the log, and each copied disk's path and the
|
||||
path of its copy here."""
|
||||
the other disks must be on storage both hosts share. on_copy is
|
||||
called with the copies ({pool, dest}: a pool here and its path) before
|
||||
libvirt makes them. Returns the mode and options used, for the log,
|
||||
and each copied disk's path and the path of its copy here."""
|
||||
state = conn.get_status()
|
||||
if state == 5:
|
||||
mode = ["offline"]
|
||||
|
|
@ -193,6 +194,8 @@ class wvmInstances(wvmConnect):
|
|||
# the TPM state is a file of the source host: a live migration carries it
|
||||
tpm_state = etree.fromstring(definitions[0]).xpath("devices/tpm/backend[@type='emulator']")
|
||||
if copies or (state == 5 and tpm_state):
|
||||
if copies and on_copy is not None:
|
||||
on_copy([{"pool": copy["pool"].name(), "dest": copy["dest"]} for copy in copies])
|
||||
note = self.migrate_running(conn, state, flags, uri, copies)
|
||||
if note:
|
||||
mode.append(note)
|
||||
|
|
|
|||
|
|
@ -269,6 +269,14 @@ class DiskCopyTestCase(unittest.TestCase):
|
|||
raise libvirt.libvirtError(message)
|
||||
source.instance.migrate3.side_effect = migrate3
|
||||
|
||||
def test_the_copies_are_told_before_libvirt_makes_them(self):
|
||||
"""A job records them: recovery removes them after a worker that stopped."""
|
||||
source, pool = Source(1, domain(disk(f"{IMAGES}/vm.qcow2"))), Pool("fast", FAST)
|
||||
told = []
|
||||
source.instance.migrate3.side_effect = lambda *args: self.assertEqual(told, [[{"pool": "fast", "dest": f"{FAST}/vm.qcow2"}]])
|
||||
migrate(destination(pool), source, {"vda": "fast"}, on_copy=told.append)
|
||||
source.instance.migrate3.assert_called_once()
|
||||
|
||||
def test_a_failed_migration_removes_the_partial_copies(self):
|
||||
source, pool = Source(1, domain(disk(f"{IMAGES}/vm.qcow2"))), Pool("fast", FAST)
|
||||
self.fail_after_copying(source, pool, "operation aborted: canceled by client")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue