From 097a91592ce9fac3564c02e874bbafb315eb0d49 Mon Sep 17 00:00:00 2001 From: catborise Date: Tue, 13 Oct 2026 07:56:19 +0300 Subject: [PATCH] fix(instances): a snapshot file another VM uses is not called left behind After a snapshot revert or delete, files libvirt could not remove are named in a warning that says nothing uses them and asks to remove them by hand. A file another VM on the same host uses (as a disk, a backing file or a snapshot file) is no longer named: removing it would break that VM. The warning now says no VM on this host uses the files. --- instances/views.py | 2 +- vrtManager/instance.py | 15 ++++++++++++--- vrtManager/tests/test_external_snapshots.py | 20 +++++++++++++++++++- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/instances/views.py b/instances/views.py index 2bbd84b9..13976c01 100755 --- a/instances/views.py +++ b/instances/views.py @@ -1383,7 +1383,7 @@ def _warn_left_behind(request, files): messages.warning( request, _( - "These snapshot files were not removed, although nothing uses them any more: %(files)s. " + "These snapshot files were not removed, although no VM on this host 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." ) diff --git a/vrtManager/instance.py b/vrtManager/instance.py index c5ce2614..f5e11259 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -1715,9 +1715,10 @@ class wvmInstance(wvmConnect): logs a warning then (on NFS it removes them as the qemu user, who needs write access to their directory). Only a snapshot's own file or an overlay on a snapshot point counts; a disk a revert detached is - kept on purpose. A file outside every storage pool is not checked, - nor is anything when the pools could not be refreshed: a stale pool - still lists the removed files.""" + kept on purpose, and a file another VM on this host uses (or one + below it) is not reported. A file outside every storage pool is not + checked, nor is anything when the pools could not be refreshed: a + stale pool still lists the removed files.""" disks, own, points = before if not self._refresh_pools_after_snapshot(): return [] @@ -1734,6 +1735,14 @@ class wvmInstance(wvmConnect): except libvirtError: # not in a pool, or removed meanwhile continue left.append(path) + if left: + # another VM on this host may use one; their own backing files + # do not keep each other, but a file in use keeps the files below it + in_use = self.paths_in_use(own=left) + for path in [path for path in left if path in in_use]: + while (path := self._backing_path(path)) and path not in in_use: + in_use.add(path) + left = [path for path in left if path not in in_use] return left @staticmethod diff --git a/vrtManager/tests/test_external_snapshots.py b/vrtManager/tests/test_external_snapshots.py index 3dcc9ec3..b778424b 100644 --- a/vrtManager/tests/test_external_snapshots.py +++ b/vrtManager/tests/test_external_snapshots.py @@ -4,7 +4,7 @@ cases are in instances/tests/test_live_snapshots.py.""" import unittest from datetime import datetime -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch from django.conf import settings @@ -440,6 +440,24 @@ class LeftBehindTestCase(unittest.TestCase): ["/pool/a.one", "/pool/vm.one.mem"], ) + def test_a_file_another_vm_uses_is_not_reported(self): + # on NFS libvirt could not remove it, but another VM uses it now + with patch.object(wvmInstance, "paths_in_use", return_value={"/pool/a.one"}) as in_use, \ + patch.object(wvmInstance, "_backing_path", return_value=None): + left = self.run_delete({"/pool/a.one", "/pool/vm.one.mem", "/pool/a.two", "/pool/a.qcow2"}) + self.assertEqual(left, ["/pool/vm.one.mem"]) + # their own backing files do not keep each other + in_use.assert_called_once_with(own=["/pool/a.one", "/pool/vm.one.mem"]) + + def test_a_file_below_one_another_vm_uses_is_not_reported(self): + # the other VM is shut off: its definition does not list the backing + # file, and the in-use top's own backing reference does not count + backing = {"/pool/a.one": "/pool/vm.one.mem"} + with patch.object(wvmInstance, "paths_in_use", return_value={"/pool/a.one"}), \ + patch.object(wvmInstance, "_backing_path", side_effect=backing.get): + left = self.run_delete({"/pool/a.one", "/pool/vm.one.mem", "/pool/a.two", "/pool/a.qcow2"}) + self.assertEqual(left, []) + def test_nothing_is_reported_when_libvirt_removed_them(self): # files still in use (base, the newer overlay) are never reported self.assertEqual(self.run_delete({"/pool/a.two", "/pool/a.qcow2"}), [])