diff --git a/vrtManager/instance.py b/vrtManager/instance.py index df14a667..27417452 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -454,6 +454,8 @@ class wvmInstances(wvmConnect): them), CD-ROM images, block devices and pool volumes. Network disks (RBD, iSCSI, ...) are reached the same way from every host and are not checked; other types (dir, nvme, ...) cannot be and count as missing. + A volume of an LVM or disk pool here counts as missing too: such + storage is each host's own, and one of the same name is other data. Pools are refreshed before a disk counts as missing, as a file another host made on shared storage is listed after that.""" disks = {} @@ -483,7 +485,8 @@ class wvmInstances(wvmConnect): continue checked.add(key) volume = self.find_volume(*key) - if volume is None: + if volume is None or ElementTree.fromstring( + volume.storagePoolLookupByVolume().XMLDesc(0)).get("type") in ("logical", "disk"): unseen.append((target, key)) continue backing = ElementTree.fromstring(volume.XMLDesc(0)).findtext("backingStore/path") diff --git a/vrtManager/tests/fakes.py b/vrtManager/tests/fakes.py index 7611360a..fb73a158 100644 --- a/vrtManager/tests/fakes.py +++ b/vrtManager/tests/fakes.py @@ -88,8 +88,12 @@ def no_volume(): class FakeVolume: - def __init__(self, backing): + def __init__(self, backing, pool_type="dir"): self.backing = backing + self.pool_type = pool_type + + def storagePoolLookupByVolume(self): + return MagicMock(XMLDesc=lambda flags: f"") def XMLDesc(self, flags=0): if self.backing is None: @@ -121,7 +125,7 @@ class FakeHost: """The destination's libvirt connection. volumes: the paths its pools list; unlisted: files its pools show after a refresh.""" - def __init__(self, existing=(), volumes=(), unlisted=(), backing=None, inactive_pools=()): + def __init__(self, existing=(), volumes=(), unlisted=(), backing=None, inactive_pools=(), pool_types=None): self.existing = set(existing) self.defined = [] self.new = [] @@ -129,12 +133,13 @@ class FakeHost: self.unlisted = set(unlisted) self.backing = backing or {} # path: the path of its backing file self.inactive_pools = set(inactive_pools) + self.pool_types = pool_types or {} # path: the type of its volume's pool, dir if not named self.refreshes = 0 def storageVolLookupByPath(self, path): if path not in self.volumes: raise no_volume() - return FakeVolume(self.backing.get(path)) + return FakeVolume(self.backing.get(path), self.pool_types.get(path, "dir")) def storagePoolLookupByName(self, name): return FakePool(self, active=name not in self.inactive_pools) diff --git a/vrtManager/tests/test_migration.py b/vrtManager/tests/test_migration.py index a951fcc1..92fd6782 100644 --- a/vrtManager/tests/test_migration.py +++ b/vrtManager/tests/test_migration.py @@ -246,6 +246,18 @@ class DestinationDisksTestCase(unittest.TestCase): with self.assertRaisesRegex(util.OperationError, "vdb \\(dir disk, cannot be checked\\)"): self.migrate(FakeHost(), xml, state=5) + def test_a_volume_of_a_host_s_own_lvm_or_disk_pool_is_missing(self): + # another host's logical volume of the same name is other data + for pool_type in ("logical", "disk"): + with self.subTest(pool_type=pool_type): + host = FakeHost(volumes={"/dev/vg/vm"}, pool_types={"/dev/vg/vm": pool_type}) + xml = with_disks("") + with self.assertRaisesRegex(util.OperationError, "vda \\(/dev/vg/vm\\)"): + self.migrate(host, xml) + host = FakeHost(volumes={"/dev/sdz"}, pool_types={"/dev/sdz": "iscsi"}) + xml = with_disks("") + self.assertEqual(len(self.migrate(host, xml).instance.calls), 1) + def test_a_volume_of_an_inactive_pool_is_missing(self): host = FakeHost(volumes={"v"}, inactive_pools={"p"}) xml = with_disks("")