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"):
|
||||
|
|
|
|||
|
|
@ -1371,6 +1371,20 @@ def revert_snapshot(request, pk):
|
|||
return _back(request, pk, "managesnapshot")
|
||||
|
||||
|
||||
def _warn_left_behind(request, files):
|
||||
"""libvirt only logs it when it cannot remove a snapshot file."""
|
||||
if files:
|
||||
messages.warning(
|
||||
request,
|
||||
_(
|
||||
"These snapshot files were not removed, although nothing uses them any more: %(files)s. "
|
||||
"On NFS libvirt removes them as the qemu user, who needs write access to their directory. "
|
||||
"Remove them by hand."
|
||||
)
|
||||
% {"files": ", ".join(files)},
|
||||
)
|
||||
|
||||
|
||||
@require_POST
|
||||
@serialize_instance_mutation
|
||||
def create_external_snapshot(request, pk):
|
||||
|
|
@ -1398,11 +1412,12 @@ def revert_external_snapshot(request, pk):
|
|||
raise PermissionDenied
|
||||
name = request.POST.get("name", "")
|
||||
try:
|
||||
instance.proxy.revert_external_snapshot(name)
|
||||
left = instance.proxy.revert_external_snapshot(name)
|
||||
except libvirtError as err:
|
||||
messages.error(request, _("Snapshot was not reverted: %(err)s") % {"err": error_text(request, err)})
|
||||
else:
|
||||
messages.success(request, _("Successful revert snapshot: ") + name)
|
||||
_warn_left_behind(request, left)
|
||||
msg = _("Revert external snapshot: %(snap)s") % {"snap": name}
|
||||
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
||||
return _back(request, pk, "managesnapshot")
|
||||
|
|
@ -1416,10 +1431,11 @@ def delete_external_snapshot(request, pk):
|
|||
raise PermissionDenied
|
||||
name = request.POST.get("name", "")
|
||||
try:
|
||||
instance.proxy.delete_external_snapshot(name)
|
||||
left = instance.proxy.delete_external_snapshot(name)
|
||||
except libvirtError as err:
|
||||
messages.error(request, _("Snapshot was not deleted: %(err)s") % {"err": error_text(request, err)})
|
||||
else:
|
||||
_warn_left_behind(request, left)
|
||||
msg = _("Delete external snapshot: %(snap)s") % {"snap": name}
|
||||
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
|
||||
return _back(request, pk, "managesnapshot")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue