"""Safety helpers for the live libvirt test suite.
Everything the suite creates is named with PREFIX, and its volumes live
in a dedicated pool. Cleanup only removes prefixed domains and the
volumes of that pool, so a broken test cannot delete other VMs or images
on a shared host. inventory() lets a suite check that nothing else
changed.
"""
import os
import libvirt
PREFIX = "wvc-test-"
POOL = "wvc-test"
POOL_PATH = "/var/lib/libvirt/wvc-test"
UNDEFINE_FLAGS = (
libvirt.VIR_DOMAIN_UNDEFINE_MANAGED_SAVE
| libvirt.VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA
| libvirt.VIR_DOMAIN_UNDEFINE_NVRAM
| libvirt.VIR_DOMAIN_UNDEFINE_CHECKPOINTS_METADATA
)
def enabled():
"""The suite creates and deletes VMs, so it only runs on an explicitly named host."""
return bool(os.environ.get("TEST_LIBVIRT_HOST"))
def ensure_pool(conn):
try:
pool = conn.storagePoolLookupByName(POOL)
except libvirt.libvirtError:
pool = conn.storagePoolDefineXML(
f"{POOL}{POOL_PATH}"
)
pool.build(0)
if not pool.isActive():
pool.create(0)
pool.refresh(0)
return pool
def cleanup(conn):
"""Remove prefixed domains and every volume of the test pool."""
for dom in conn.listAllDomains():
if not dom.name().startswith(PREFIX):
continue
persistent = dom.isPersistent()
if dom.isActive():
dom.destroy()
if persistent:
dom.undefineFlags(UNDEFINE_FLAGS)
try:
pool = conn.storagePoolLookupByName(POOL)
except libvirt.libvirtError:
return
if pool.isActive():
pool.refresh(0)
for vol in pool.listAllVolumes():
vol.delete(0)
def remove_pool(conn):
cleanup(conn)
try:
pool = conn.storagePoolLookupByName(POOL)
except libvirt.libvirtError:
return
if pool.isActive():
pool.destroy()
pool.delete(0)
pool.undefine()
def inventory(conn):
"""Domains and volumes outside the test namespace."""
domains = {(d.name(), d.UUIDString()) for d in conn.listAllDomains() if not d.name().startswith(PREFIX)}
volumes = set()
for pool in conn.listAllStoragePools():
if pool.name() == POOL or not pool.isActive():
continue
pool.refresh(0)
volumes.update((pool.name(), v.name()) for v in pool.listAllVolumes())
return domains, volumes
def create_volume(conn, name, size_mib=64):
"""A qcow2 volume in the test pool; returns its path."""
assert name.startswith(PREFIX)
pool = conn.storagePoolLookupByName(POOL)
vol = pool.createXML(
f"{name}.qcow2{size_mib}"
"",
0,
)
return vol.path()
OVMF_CODE = "/usr/share/edk2/ovmf/OVMF_CODE.fd"
def define_vm(conn, name, disk_paths, uefi=False, extra_devices=""):
"""A small q35 VM without an OS; disks are attached as vda, vdb, ..."""
assert name.startswith(PREFIX)
disks = "".join(
f""
f""
for i, path in enumerate(disk_paths)
)
firmware = (
"hvm"
f"{OVMF_CODE}"
if uefi
else "hvm"
)
return conn.defineXML(
f"{name}1281"
f"{firmware}{disks}{extra_devices}"
)
def volume_exists(conn, path):
pool = conn.storagePoolLookupByName(POOL)
pool.refresh(0)
return os.path.basename(path) in pool.listVolumes()