1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-10 04:06:47 +00:00

fix(vrtManager): a failed NVRAM copy aborts its libvirt streams

libvirt requires a stream that did not finish to be aborted before it is
freed. The NVRAM download of a shut-off move and upload_volume (NVRAM
upload, cloud-init seed) freed theirs after a failed send, receive or
finish without aborting, on the shared host connection. They abort now;
upload_volume opens its stream before it makes the volume, so the
cleanup has both.
This commit is contained in:
catborise 2026-11-04 16:37:55 +03:00
parent 3911e9e238
commit d787d587bc
4 changed files with 21 additions and 4 deletions

View file

@ -1039,17 +1039,20 @@ class wvmConnect(object):
"""A new raw volume in pool holding data, written through a libvirt
stream, which every connection type carries. A failed write leaves
no volume."""
stream = self.wvm.newStream(0)
vol = pool.createXML(
f"<volume><name>{util.xml_escape(name)}</name><capacity>{len(data)}</capacity>"
"<target><format type='raw'/></target></volume>",
0,
)
try:
stream = self.wvm.newStream(0)
vol.upload(stream, 0, len(data), 0)
stream.sendAll(lambda _stream, size, buffer: buffer.read(size), io.BytesIO(data))
stream.finish()
except Exception:
# libvirt: a stream that did not finish is aborted before it is freed
with contextlib.suppress(libvirtError):
stream.abort()
vol.delete(0)
raise
return vol

View file

@ -427,9 +427,14 @@ class wvmInstances(wvmConnect):
return None
chunks = []
stream = conn.wvm.newStream(0)
vol.download(stream, 0, 0, 0)
stream.recvAll(lambda _stream, data, out: out.append(data), chunks)
stream.finish()
try:
vol.download(stream, 0, 0, 0)
stream.recvAll(lambda _stream, data, out: out.append(data), chunks)
stream.finish()
except Exception:
with contextlib.suppress(libvirtError):
stream.abort()
raise
self.upload_volume(target, name, b"".join(chunks))
return name

View file

@ -78,6 +78,7 @@ class UploadVolumeTestCase(unittest.TestCase):
with self.assertRaises(RuntimeError):
conn.upload_volume(pool, "a.iso", b"data")
pool.createXML.return_value.delete.assert_called_once_with(0)
conn.wvm.newStream.return_value.abort.assert_called_once_with()
class DeleteWithDisksTestCase(unittest.TestCase):

View file

@ -465,6 +465,14 @@ class NvramCopyTestCase(unittest.TestCase):
copy.upload.assert_called_once_with(self.dest.wvm.newStream.return_value, 0, 4, 0)
self.assertEqual(self.host.defined, ["<domain><name>vm</name></domain>"])
def test_a_failed_download_aborts_its_stream(self):
stream = self.source.wvm.newStream.return_value
stream.recvAll.side_effect = libvirt.libvirtError("download failed")
with self.assertRaises(libvirt.libvirtError):
self.move()
stream.abort.assert_called_once_with()
self.dest_pool.createXML.assert_not_called()
def test_a_shared_directory_is_not_copied(self):
self.shared.stop()
with mock.patch.object(wvmInstances, "same_directory", return_value=True):