1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00
webvirtcloud/vrtManager/storage.py
catborise 764cbb3c43 chore: remove code nothing uses
Each was checked with git grep across every tracked file (templates, JS,
shell, unit files and the extensionless console daemons) and for
dynamic use (URL names, template names, registries):

- vrtManager: host_is_up, is_kvm_supported, get_emulators,
  get_file_extensions, both get_network_forward, get_snapshots,
  get_storages_images, get_os_type, get_host_arch, the get_pretty_* trio
  and, in util, is_kvm_available, randomMAC, get_max_vcpus, compareMAC,
  pretty_mem, pretty_bytes and validate_uuid; with them the port
  constants only host_is_up read. Some were reached only by their tests,
  which go too.
- The flavor edit page: nothing linked to it; flavors are created and
  deleted on the Flavors page and edited through the API.
- computes.validate_name, UserForm.identity_has_errors, the has_perm
  template tag, the ALLOW_EMPTY_PASSWORD setting nothing reads,
  hidePleaseWaitDialog, default_app_config (ignored since Django 4.1)
  and the Python < 3.6 lines of secret_generator.py.
2026-11-06 10:53:37 +03:00

393 lines
14 KiB
Python

from django.utils.translation import gettext_noop
import contextlib
import libvirt
from vrtManager import util
from vrtManager.connection import wvmConnect
def volume_details(vol):
"""A volume's name, capacity (size), allocation and format, from its XML:
one libvirt call, which refreshes the volume as info() does. Logical and
iSCSI volumes have no format (None)."""
def read(doc):
return {
"name": util.get_xpath(doc, "/volume/name"),
"size": int(util.get_xpath(doc, "/volume/capacity")),
"allocation": int(util.get_xpath(doc, "/volume/allocation")),
"type": util.get_xpath(doc, "/volume/target/format/@type"),
}
return util.get_xml_path(vol.XMLDesc(0), func=read)
class wvmStorages(wvmConnect):
def get_storages_info(self):
"""Each pool's state, autostart, type, path (an RBD pool's Ceph pool),
volume count, size, free space and the percent used. An active pool
is refreshed first: libvirt measures its space only when it is
refreshed, so two hosts sharing a pool's storage told different free
space, each as of its own last refresh."""
get_storages = self.get_storages()
storages = []
for pool in get_storages:
stg = self.get_storage(pool)
stg_status = stg.isActive()
if stg_status:
with contextlib.suppress(libvirt.libvirtError): # refused while a volume is cloned in it
stg.refresh(0)
xml = stg.XMLDesc(0)
stg_vol = len(stg.listVolumes()) if stg_status else None
_state, stg_size, _allocation, stg_free = stg.info()
storages.append(
{
"name": pool,
"status": stg_status,
"autostart": stg.autostart(),
"type": util.get_xml_path(xml, "/pool/@type"),
"path": util.get_xml_path(xml, "/pool/target/path") or util.get_xml_path(xml, "/pool/source/name"),
"volumes": stg_vol,
"size": stg_size,
"free": stg_free,
"used_percent": (stg_size - stg_free) * 100 / stg_size if stg_size else 0,
}
)
return storages
def define_storage(self, xml, flags):
"""Define the pool, start it with flags and set it to autostart. A
pool that does not start is undefined again, so its name stays free
for another try."""
stg = self.wvm.storagePoolDefineXML(xml, 0)
try:
stg.create(flags)
except libvirt.libvirtError:
stg.undefine()
raise
stg.setAutostart(1)
return stg
def get_pool_by_target(self, target):
pool_names = self.get_storages()
for pool_name in pool_names:
stg = wvmStorage(self.host, self.login, self.passwd, self.conn, pool_name)
if stg.get_target_path() == target:
return self.get_storage(pool_name)
if stg.get_type() == "rbd" and stg.get_source_name() == target:
return self.get_storage(pool_name)
return None
def create_storage(self, stg_type, name, source, target):
xml = f"""<pool type='{stg_type}'>
<name>{name}</name>"""
if stg_type == "logical":
xml += f"""<source>
<device path='{source}'/>
<name>{name}</name>
<format type='lvm2'/>
</source>"""
if stg_type == "logical":
target = f"/dev/{name}"
xml += f"""
<target>
<path>{target}</path>
</target>
</pool>"""
# builds the dir pool's directory or the logical pool's volume group
return self.define_storage(xml, libvirt.VIR_STORAGE_POOL_CREATE_WITH_BUILD)
def create_storage_ceph(
self, stg_type, name, ceph_pool, ceph_host, ceph_user, secret
):
ceph_pool, ceph_host, ceph_user, secret = map(util.xml_escape, (ceph_pool, ceph_host, ceph_user, secret))
xml = f"""
<pool type='{stg_type}'>
<name>{name}</name>
<source>
<name>{ceph_pool}</name>
<host name='{ceph_host}' port='6789'/>
<auth username='{ceph_user}' type='ceph'>
<secret uuid='{secret}'/>
</auth>
</source>
</pool>
"""
self.define_storage(xml, 0)
def create_storage_netfs(
self, stg_type, name, netfs_host, source, source_format, target
):
netfs_host, source_format = util.xml_escape(netfs_host), util.xml_escape(source_format)
xml = f"""
<pool type='{stg_type}'>
<name>{name}</name>
<source>
<host name='{netfs_host}'/>
<dir path='{source}'/>
<format type='{source_format}'/>
</source>
<target>
<path>{target}</path>
</target>
</pool>
"""
# builds the mount point
return self.define_storage(xml, libvirt.VIR_STORAGE_POOL_CREATE_WITH_BUILD)
class wvmStorage(wvmConnect):
def __init__(self, host, login, passwd, conn, pool):
wvmConnect.__init__(self, host, login, passwd, conn)
self.pool = self.get_storage(pool)
def get_name(self):
return self.pool.name()
def get_status(self):
status = [
"Not running",
"Initializing pool, not available",
"Running normally",
"Running degraded",
]
try:
return status[self.pool.info()[0]]
except ValueError:
return "Unknown"
def get_size(self):
return [self.pool.info()[1], self.pool.info()[3]]
def _XMLDesc(self, flags):
return self.pool.XMLDesc(flags)
def _createXML(self, xml, flags):
self.pool.createXML(xml, flags)
def _createXMLFrom(self, xml, vol, flags):
self.pool.createXMLFrom(xml, vol, flags)
def _define(self, xml):
return self.wvm.storagePoolDefineXML(xml, 0)
def is_active(self):
return self.pool.isActive()
def get_uuid(self):
return self.pool.UUIDString()
def start(self):
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(gettext_noop("Pool %(name)s holds disks of the running VMs %(vms)s; shut them down first"), name=name, vms=', '.join(vms))
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(gettext_noop("VMs %(vms)s have disks in pool %(name)s by its name and would no longer start; detach those disks first"), vms=', '.join(vms), name=name)
self.pool.undefine()
def get_autostart(self):
return self.pool.autostart()
def set_autostart(self, value):
self.pool.setAutostart(value)
def get_type(self):
return util.get_xml_path(self._XMLDesc(0), "/pool/@type")
def get_target_path(self):
return util.get_xml_path(self._XMLDesc(0), "/pool/target/path")
def get_source_name(self):
return util.get_xml_path(self._XMLDesc(0), "/pool/source/name")
def get_allocation(self):
return int(util.get_xml_path(self._XMLDesc(0), "/pool/allocation"))
def get_available(self):
return int(util.get_xml_path(self._XMLDesc(0), "/pool/available"))
def get_capacity(self):
return int(util.get_xml_path(self._XMLDesc(0), "/pool/capacity"))
def get_rbd_source(self):
def hosts(doc):
hosts_array = []
for host in doc.xpath("/pool/source/host"):
name = host.get("name")
if name:
port = host.get("port")
if port:
hosts_array.append({"hostname": name, "hostport": port})
else:
hosts_array.append({"hostname": name})
name = doc.get("name")
auth = doc.xpath("/pool/source/auth")
auth_type = auth[0].get("type")
auth_user = auth[0].get("username")
auth_uuid = auth[0].xpath("secret/@uuid")[0]
return {
"name": name,
"auth_type": auth_type,
"auth_user": auth_user,
"auth_uuid": auth_uuid,
"hosts": hosts_array,
}
return util.get_xml_path(self._XMLDesc(0), func=hosts)
def get_volumes(self):
return self.pool.listVolumes()
def get_volume(self, name):
return self.pool.storageVolLookupByName(name)
def _vol_XMLDesc(self, name):
vol = self.get_volume(name)
return vol.XMLDesc(0)
def del_volume(self, name):
"""libvirt deletes any volume; one a VM still needs is refused."""
vol = self.pool.storageVolLookupByName(name)
if vol.path() in self.paths_in_use():
raise util.OperationError(gettext_noop("Volume %(name)s is in use: a VM's disk, a backing file or a snapshot's file"), name=name)
vol.delete(0)
def get_volume_format_type(self, name):
vol_xml = self._vol_XMLDesc(name)
return util.get_xml_path(vol_xml, "/volume/target/format/@type")
def get_volume_type(self, name):
vol_xml = self._vol_XMLDesc(name)
return util.get_xml_path(vol_xml, "/volume/@type")
def refresh(self):
return self.pool.refresh(0)
def get_volume_details(self, volname):
"""The volume's details (volume_details), the pool refreshed first."""
with contextlib.suppress(libvirt.libvirtError): # refused while a volume is cloned in the pool
self.refresh()
return volume_details(self.get_volume(volname))
def update_volumes(self):
"""Every volume's details (volume_details) by name, the pool refreshed
first: two calls and one per volume, where a lookup, info() and the
XML of each took six. A volume deleted meanwhile is left out."""
with contextlib.suppress(libvirt.libvirtError): # refused while a volume is cloned in the pool
self.refresh()
volumes = []
for vol in self.pool.listAllVolumes(0):
try:
volumes.append(volume_details(vol))
except libvirt.libvirtError as err:
if err.get_error_code() != libvirt.VIR_ERR_NO_STORAGE_VOL:
raise
return sorted(volumes, key=lambda volume: volume["name"])
def create_volume(
self,
name,
size,
vol_fmt="qcow2",
metadata=False,
disk_owner_uid=0,
disk_owner_gid=0,
):
size = int(size) * 1073741824
storage_type = self.get_type()
alloc = size
if vol_fmt == "unknown":
vol_fmt = "raw"
if storage_type == "dir":
name += f".{vol_fmt}" if vol_fmt in ("qcow", "qcow2") else ".img"
alloc = 0
xml = f"""
<volume>
<name>{util.xml_escape(name)}</name>
<capacity>{size}</capacity>
<allocation>{alloc}</allocation>
<target>
<format type='{vol_fmt}'/>
<permissions>
<owner>{disk_owner_uid}</owner>
<group>{disk_owner_gid}</group>
<mode>0644</mode>
<label>virt_image_t</label>
</permissions>"""
if vol_fmt == "qcow2":
xml += """
<compat>1.1</compat>
<features>
<lazy_refcounts/>
</features>"""
xml += """</target>
</volume>"""
self._createXML(xml, metadata)
return name
def clone_name(self, target_file, vol_fmt, file_suffix="img"):
"""The name clone_volume gives a copy: a dir pool adds the format's suffix."""
if self.get_type() != "dir":
return target_file
if vol_fmt in ("qcow", "qcow2"):
return f"{target_file}.{vol_fmt}"
return f"{target_file}.{file_suffix}" if file_suffix else target_file
def clone_volume(
self,
name,
target_file,
vol_fmt=None,
metadata=False,
mode="0644",
file_suffix="img",
disk_owner_uid=0,
disk_owner_gid=0,
):
vol = self.get_volume(name)
if not vol_fmt:
vol_fmt = self.get_volume_format_type(name)
target_file = self.clone_name(target_file, vol_fmt, file_suffix)
xml = f"""
<volume>
<name>{target_file}</name>
<capacity>0</capacity>
<allocation>0</allocation>
<target>
<format type='{vol_fmt}'/>
<permissions>
<owner>{disk_owner_uid}</owner>
<group>{disk_owner_gid}</group>
<mode>{mode}</mode>
<label>virt_image_t</label>
</permissions>"""
if vol_fmt == "qcow2":
xml += """
<compat>1.1</compat>
<features>
<lazy_refcounts/>
</features>"""
xml += """ </target>
</volume>"""
self._createXMLFrom(xml, vol, metadata)
return target_file