mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-09 03:36:48 +00:00
fix(instances): destroy and volume delete no longer lose disks
Destroy: - Stops every active state, not only running; a paused VM used to keep running as a transient domain after the undefine. - Undefines with MANAGED_SAVE and SNAPSHOTS_METADATA, before any disk is deleted. A managed-save image used to make the undefine fail after the disks were already gone; now a failed undefine deletes nothing. - Keeps disks that another domain references, directly or as a backing file anywhere in its chain (running domains' backingStore, and the backingStore of the volumes in the active pools for stopped ones), and says so; they used to be deleted with the VM. Volume delete: - Detaches from the live domain in every active state, including paused. - Deletes the volume only after neither the live nor the persistent definition has the disk (bounded wait for the guest to release it), and refuses a volume another VM references. Otherwise the volume is kept and the user is told why. Both hold the compute exclusively for the usage scan and the deletion, so another VM cannot attach the disk in between; the destroy confirmation page (GET) takes no lock. Live tests reproduce each case on a RHEL qemu-kvm host and fail on the previous code; unit tests cover the destroy ordering and the locks.
This commit is contained in:
parent
dbbf2ce2e8
commit
ffb2ce6bfa
4 changed files with 280 additions and 19 deletions
|
|
@ -838,14 +838,28 @@ class wvmInstance(wvmConnect):
|
|||
0
|
||||
].getparent()
|
||||
xml_disk = etree.tostring(disk_el).decode()
|
||||
devices = tree.find("devices")
|
||||
devices.remove(disk_el)
|
||||
|
||||
if self.get_status() == 1:
|
||||
# Paused and other active states need the live detach too.
|
||||
if self.instance.isActive():
|
||||
self.instance.detachDeviceFlags(xml_disk, VIR_DOMAIN_AFFECT_LIVE)
|
||||
self.instance.detachDeviceFlags(xml_disk, VIR_DOMAIN_AFFECT_CONFIG)
|
||||
if self.get_status() == 5:
|
||||
self.instance.detachDeviceFlags(xml_disk, VIR_DOMAIN_AFFECT_CONFIG)
|
||||
self.instance.detachDeviceFlags(xml_disk, VIR_DOMAIN_AFFECT_CONFIG)
|
||||
|
||||
def has_disk(self, target_dev):
|
||||
"""True while the live or the persistent definition still has target_dev."""
|
||||
for flags in (0, VIR_DOMAIN_XML_INACTIVE):
|
||||
tree = etree.fromstring(self._XMLDesc(flags))
|
||||
if tree.xpath("./devices/disk/target[@dev=$dev]", dev=target_dev):
|
||||
return True
|
||||
return False
|
||||
|
||||
def wait_disk_detached(self, target_dev, timeout=10):
|
||||
"""A live detach completes only when the guest releases the disk."""
|
||||
deadline = time.monotonic() + timeout
|
||||
while self.has_disk(target_dev):
|
||||
if time.monotonic() >= deadline:
|
||||
return False
|
||||
time.sleep(0.5)
|
||||
return True
|
||||
|
||||
def edit_disk(
|
||||
self,
|
||||
|
|
@ -1294,6 +1308,39 @@ class wvmInstance(wvmConnect):
|
|||
iso.append(img)
|
||||
return iso
|
||||
|
||||
def paths_used_by_other_domains(self):
|
||||
"""Paths another domain uses as a disk or as a backing file of one."""
|
||||
uuid = self.instance.UUIDString()
|
||||
used = set()
|
||||
for dom in self.wvm.listAllDomains():
|
||||
if dom.UUIDString() == uuid:
|
||||
continue
|
||||
for flags in (0, VIR_DOMAIN_XML_INACTIVE):
|
||||
tree = etree.fromstring(dom.XMLDesc(flags))
|
||||
# "//" also reaches backingStore chains in a running domain's XML
|
||||
used.update(tree.xpath("./devices/disk//source/@file|./devices/disk//source/@dev"))
|
||||
# A stopped domain's XML has no backing chain; the volumes have it.
|
||||
# Any volume backed by a path keeps that path, even an unattached one.
|
||||
for pool in self.wvm.listAllStoragePools():
|
||||
if not pool.isActive():
|
||||
continue
|
||||
with contextlib.suppress(libvirtError):
|
||||
pool.refresh(0)
|
||||
for vol in pool.listAllVolumes():
|
||||
with contextlib.suppress(libvirtError):
|
||||
used.update(etree.fromstring(vol.XMLDesc(0)).xpath("./backingStore/path/text()"))
|
||||
return used
|
||||
|
||||
def split_disk_paths_by_use(self):
|
||||
"""This VM's disk paths, split into (only this VM, also used by another domain)."""
|
||||
self.refresh_instance_pools()
|
||||
own = [disk["path"] for disk in self.get_disk_devices() if disk["path"]]
|
||||
used_elsewhere = self.paths_used_by_other_domains()
|
||||
return (
|
||||
[path for path in own if path not in used_elsewhere],
|
||||
[path for path in own if path in used_elsewhere],
|
||||
)
|
||||
|
||||
def delete_all_disks(self):
|
||||
self.refresh_instance_pools()
|
||||
disks = self.get_disk_devices()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue