mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-10 04:06:47 +00:00
fix: a network, pool or secret a VM needs is not stopped or deleted
Stopping a network cut the running VMs on it off, deleting one or a secret kept the VMs that name it from starting, and stopping a pool took the disks of running VMs away, all without a word. Each is now refused with the VMs (and, for a secret, the pools) that need it: stopping while a running VM uses it, deleting while a VM's definition names it. A disk given by its file path does not need its pool's definition. The API's pool stop is refused the same way. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ef1faf57c8
commit
887b807451
9 changed files with 268 additions and 3 deletions
|
|
@ -955,6 +955,17 @@ class wvmConnect(object):
|
|||
instances.append(name)
|
||||
return instances
|
||||
|
||||
def domains_using(self, match, running_only=False):
|
||||
"""Names of the domains with a definition, parsed, that match(tree)
|
||||
holds for: the live and the persistent one of each domain, or with
|
||||
running_only the live one of each running domain."""
|
||||
listing = libvirt.VIR_CONNECT_LIST_DOMAINS_ACTIVE if running_only else 0
|
||||
flags = (0,) if running_only else (0, libvirt.VIR_DOMAIN_XML_INACTIVE)
|
||||
return sorted(
|
||||
dom.name() for dom in self.wvm.listAllDomains(listing)
|
||||
if any(match(etree.fromstring(dom.XMLDesc(f))) for f in flags)
|
||||
)
|
||||
|
||||
def paths_in_use(self, skip_uuid=None, own=()):
|
||||
"""Paths (names for network disks) a domain uses as a disk or a
|
||||
backing file of one, or as a file of its snapshots (overlays, memory,
|
||||
|
|
|
|||
|
|
@ -161,10 +161,25 @@ class wvmNetwork(wvmConnect):
|
|||
def start(self):
|
||||
self.net.create()
|
||||
|
||||
def _vms_on_it(self, running_only):
|
||||
name = self.net.name()
|
||||
return self.domains_using(
|
||||
lambda domain: domain.xpath("devices/interface[@type='network']/source[@network=$name]", name=name),
|
||||
running_only,
|
||||
)
|
||||
|
||||
def stop(self):
|
||||
"""Refused while a running VM is on it: that VM would be cut off."""
|
||||
if vms := self._vms_on_it(running_only=True):
|
||||
raise util.OperationError(f"Network {self.net.name()} is in use by the running VMs {', '.join(vms)}; "
|
||||
"shut them down or remove their network cards first")
|
||||
self.net.destroy()
|
||||
|
||||
def delete(self):
|
||||
"""Refused while a VM's definition names it: that VM would no longer start."""
|
||||
if vms := self._vms_on_it(running_only=False):
|
||||
raise util.OperationError(f"VMs {', '.join(vms)} use network {self.net.name()} and would no longer "
|
||||
"start; remove their network cards first")
|
||||
self.net.undefine()
|
||||
|
||||
def update(self, command, section, xml, parentIndex, flags=0):
|
||||
|
|
|
|||
|
|
@ -155,9 +155,27 @@ class wvmStorage(wvmConnect):
|
|||
return self.pool.create(0)
|
||||
|
||||
def stop(self):
|
||||
"""Refused while it holds a disk of a running VM, named by the pool
|
||||
or by a volume's path: that VM would lose the disk."""
|
||||
name = self.pool.name()
|
||||
paths = {vol.path() for vol in self.pool.listAllVolumes()}
|
||||
vms = self.domains_using(
|
||||
lambda domain: domain.xpath("devices/disk/source[@pool=$name]", name=name)
|
||||
or paths.intersection(self._source_paths(domain)),
|
||||
running_only=True,
|
||||
)
|
||||
if vms:
|
||||
raise util.OperationError(f"Pool {name} holds disks of the running VMs {', '.join(vms)}; "
|
||||
"shut them down first")
|
||||
return self.pool.destroy()
|
||||
|
||||
def delete(self):
|
||||
"""Refused while a VM's definition names it for a disk: that VM would
|
||||
no longer start. A disk given by its file path does not need it."""
|
||||
name = self.pool.name()
|
||||
if vms := self.domains_using(lambda domain: domain.xpath("devices/disk/source[@pool=$name]", name=name)):
|
||||
raise util.OperationError(f"VMs {', '.join(vms)} have disks in pool {name} by its name and would no "
|
||||
"longer start; detach those disks first")
|
||||
self.pool.undefine()
|
||||
|
||||
def get_autostart(self):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import base64
|
||||
|
||||
from lxml import etree
|
||||
from vrtManager import util
|
||||
from vrtManager.connection import wvmConnect
|
||||
|
||||
# the element of <usage> that holds the value, per usage type
|
||||
|
|
@ -22,5 +23,15 @@ class wvmSecrets(wvmConnect):
|
|||
secrt.setValue(base64.b64decode(value, validate=True))
|
||||
|
||||
def delete_secret(self, uuid):
|
||||
"""Refused while a VM disk or a pool names it, by UUID or by usage:
|
||||
without it that VM or pool would no longer start."""
|
||||
secrt = self.get_secret(uuid)
|
||||
refs = "//secret[@uuid=$uuid or @usage=$usage]"
|
||||
usage = secrt.usageID() or ""
|
||||
vms = self.domains_using(lambda domain: domain.xpath(f"devices/disk{refs}", uuid=uuid, usage=usage))
|
||||
pools = sorted(pool.name() for pool in self.wvm.listAllStoragePools()
|
||||
if etree.fromstring(pool.XMLDesc(0)).xpath(f"source{refs}", uuid=uuid, usage=usage))
|
||||
if vms or pools:
|
||||
users = "; ".join(f"{kind} {', '.join(names)}" for kind, names in (("VMs", vms), ("pools", pools)) if names)
|
||||
raise util.OperationError(f"Secret {uuid} is used by {users}; remove it from them first")
|
||||
secrt.undefine()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue