mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-11 12:46:49 +00:00
fix(instances): name snapshot files libvirt could not remove
When libvirt cannot remove a snapshot file it merged or discarded, it only logs a warning and the delete or revert still succeeds. On NFS it removes them as the qemu user, so an image directory the qemu user may not write to left every merged overlay on disk without a word. Revert and delete now compare the files in use before and after and show a warning naming the ones still on disk. Only a snapshot's own files (overlays, memory) and overlays on a snapshot point count, and a disk the revert detached is never named, so a kept disk is not offered for removal.
This commit is contained in:
parent
ff1508b3c2
commit
3fd422db7e
6 changed files with 194 additions and 11 deletions
|
|
@ -54,6 +54,12 @@ class LiveExternalSnapshotTestCase(SimpleTestCase):
|
|||
dom.suspend()
|
||||
return dom, wvmInstance(*self.args, None, uuid=dom.UUIDString())
|
||||
|
||||
def pool_files(self, name):
|
||||
"""The test pool's files whose names start with the VM's name."""
|
||||
pool = self.conn.storagePoolLookupByName(livetest.POOL)
|
||||
pool.refresh(0)
|
||||
return {vol for vol in pool.listVolumes() if vol.startswith(P + name + ".")}
|
||||
|
||||
def top(self, dom):
|
||||
return etree.fromstring(dom.XMLDesc(0)).xpath("string(devices/disk[@device='disk']/source/@file)")
|
||||
|
||||
|
|
@ -66,10 +72,11 @@ class LiveExternalSnapshotTestCase(SimpleTestCase):
|
|||
base = self.top(dom)
|
||||
vm.create_external_snapshot("one", "first")
|
||||
vm.create_external_snapshot("two")
|
||||
vm.revert_external_snapshot("one")
|
||||
self.assertEqual(vm.revert_external_snapshot("one"), [], "files left behind")
|
||||
self.assertEqual(dom.state()[0], expected)
|
||||
vm.delete_external_snapshot("two")
|
||||
vm.delete_external_snapshot("one")
|
||||
self.assertEqual(vm.delete_external_snapshot("two"), [], "files left behind")
|
||||
self.assertEqual(vm.delete_external_snapshot("one"), [], "files left behind")
|
||||
self.assertEqual(self.pool_files("snap-" + state), {os.path.basename(base)}, "an overlay stayed on disk")
|
||||
self.assertEqual(dom.snapshotNum(0), 0)
|
||||
self.assertEqual(dom.state()[0], expected)
|
||||
self.assertTrue(livetest.volume_exists(self.conn, base))
|
||||
|
|
@ -82,7 +89,7 @@ class LiveExternalSnapshotTestCase(SimpleTestCase):
|
|||
dom.suspend()
|
||||
vm.revert_external_snapshot("m")
|
||||
self.assertEqual(dom.state()[0], libvirt.VIR_DOMAIN_RUNNING)
|
||||
vm.delete_external_snapshot("m")
|
||||
self.assertEqual(vm.delete_external_snapshot("m"), [])
|
||||
self.assertFalse(livetest.volume_exists(self.conn, mem))
|
||||
|
||||
def test_uefi_vm_keeps_its_loader(self):
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class SnapshotViewsTestCase(TestCase):
|
|||
admin = get_user_model().objects.create_superuser("snap_admin", "s@example.com", "pw")
|
||||
self.client.force_login(admin)
|
||||
|
||||
def post(self, view, data, error=None):
|
||||
def post(self, view, data, error=None, left=()):
|
||||
with patch("instances.models.wvmInstance") as mock_wvm:
|
||||
proxy = mock_wvm.return_value
|
||||
method = {
|
||||
|
|
@ -36,8 +36,10 @@ class SnapshotViewsTestCase(TestCase):
|
|||
"delete_snapshot": proxy.snapshot_delete,
|
||||
"snapshot": proxy.create_snapshot,
|
||||
}[view]
|
||||
method.return_value = list(left) # files revert and delete left behind
|
||||
if error:
|
||||
method.side_effect = util.OperationError(error)
|
||||
self.client.cookies.pop("messages", None) # only this request's messages
|
||||
response = self.client.post(
|
||||
reverse(f"instances:{view}", args=[self.instance.id]),
|
||||
data,
|
||||
|
|
@ -60,6 +62,16 @@ class SnapshotViewsTestCase(TestCase):
|
|||
for power in ("start", "force_shutdown", "shutdown"):
|
||||
getattr(proxy, power).assert_not_called()
|
||||
|
||||
def test_files_left_behind_are_named_in_a_warning(self):
|
||||
for view in ("revert_external_snapshot", "delete_external_snapshot"):
|
||||
with self.subTest(view):
|
||||
_, _, messages = self.post(view, {"name": "a"}, left=["/pool/a.one", "/pool/vm.one.mem"])
|
||||
warning = [m for m in messages if "/pool/a.one, /pool/vm.one.mem" in m]
|
||||
self.assertEqual(len(warning), 1, messages)
|
||||
self.assertIn("qemu user", warning[0])
|
||||
_, _, messages = self.post(view, {"name": "a"})
|
||||
self.assertFalse([m for m in messages if "not removed" in m], messages)
|
||||
|
||||
def test_refusals_become_messages(self):
|
||||
for view in ("create_external_snapshot", "revert_external_snapshot", "delete_external_snapshot",
|
||||
"revert_snapshot", "delete_snapshot", "snapshot"):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue