1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-09 03:36:48 +00:00

perf(instances): the VM page reads each disk, the state and host info once

Measured on a VM with two disks, a CD-ROM and a NIC: 70 libvirt calls per
page, now 43. Within the page's cached_reads block the VM's state, the
host's info and domain capabilities, and each disk path's volume are read
once; the running and the persistent disk lists share a path's volume.
The ISO list takes the active pools in one call instead of looking each up
by name; it still refreshes them, so an image copied in by hand shows up.

The opt-in DRBD status waits at most 10 s for the compute's ssh, then
leaves the status unknown instead of holding the page.
This commit is contained in:
catborise 2026-10-09 08:21:28 +03:00
parent 95b4418c96
commit 439f24041e
6 changed files with 278 additions and 52 deletions

View file

@ -1,14 +1,18 @@
"""wvmInstance.cached_reads(): inside the block the domain XML (per flags) and
the host capabilities are read from libvirt once; outside it every read goes
to libvirt, as code that changes the VM needs."""
"""wvmInstance.cached_reads(): inside the block the domain XML (per flags),
the VM's state, the host capabilities and info, and each disk's volume are
read from libvirt once; outside it every read goes to libvirt, as code that
changes the VM needs."""
import unittest
from unittest.mock import MagicMock, patch
from django.conf import settings
if not settings.configured:
settings.configure(MAC_OUI="52:54:10")
from libvirt import libvirtError
from vrtManager.connection import wvmConnect
from vrtManager.instance import wvmInstance
@ -17,6 +21,10 @@ class FakeDomain:
self.reads = []
self.fail = False
def info(self):
self.reads.append("info")
return [1, 0, 0, 0, 0]
def XMLDesc(self, flags):
if self.fail:
raise RuntimeError("libvirt error")
@ -29,6 +37,10 @@ class FakeConn:
self.caps = 0
self.defined = []
def getInfo(self):
self.caps += 1
return ["x86_64", 2048, 4, 0, 1, 2, 2, 1]
def getCapabilities(self):
self.caps += 1
return "<capabilities/>"
@ -111,3 +123,100 @@ class CachedReadsTestCase(unittest.TestCase):
b._XMLDesc(0)
b._XMLDesc(0)
self.assertEqual((a.instance.reads, b.instance.reads), ([0], [0, 0]))
def test_the_state_and_host_info_are_read_once(self):
p = proxy()
with p.cached_reads():
for _ in range(2):
p.get_status()
p.get_max_memory()
p.get_max_cpus()
self.assertEqual((p.instance.reads, p.wvm.caps), (["info"], 1))
p.get_status()
self.assertEqual(p.instance.reads, ["info", "info"])
def test_domain_capabilities_are_read_once_per_arch_and_machine(self):
p = proxy()
with patch.object(wvmConnect, "get_dom_cap_xml", return_value="<domainCapabilities/>") as read:
with p.cached_reads():
for _ in range(2):
p.get_dom_cap_xml("x86_64", "q35")
p.get_dom_cap_xml("x86_64", "pc")
p.get_dom_cap_xml("x86_64", "q35")
self.assertEqual([c.args for c in read.call_args_list], [("x86_64", "q35"), ("x86_64", "pc"), ("x86_64", "q35")])
def volume_proxy(paths):
"""A proxy whose host has volumes at paths (pool "pool", size 10, used 4)."""
p = proxy()
p.lookups = []
def lookup(path):
p.lookups.append(path)
if path not in paths:
raise libvirtError("no storage vol with matching path")
vol = MagicMock()
vol.name.return_value = path.rsplit("/", 1)[-1]
vol.info.return_value = [0, 10, 4]
vol.storagePoolLookupByVolume.return_value.name.return_value = "pool"
return vol
p.get_volume_by_path = lookup
return p
class VolumeDetailsTestCase(unittest.TestCase):
def test_inside_the_block_each_path_is_looked_up_once(self):
p = volume_proxy({"/p/a.qcow2", "/p/b.raw"})
with p.cached_reads():
for _ in range(2):
self.assertEqual(p._volume_details("/p/a.qcow2"), ("a.qcow2", 10, 4, "pool"))
p._volume_details("/p/b.raw")
self.assertIsNone(p._volume_details("/dev/sdx"))
self.assertEqual(p.lookups, ["/p/a.qcow2", "/p/b.raw", "/dev/sdx"])
def test_outside_the_block_and_after_a_change_the_volume_is_read_again(self):
p = volume_proxy({"/p/a.qcow2"})
p._volume_details("/p/a.qcow2")
with p.cached_reads():
p._volume_details("/p/a.qcow2")
p._defineXML("<domain/>")
p._volume_details("/p/a.qcow2")
p._volume_details("/p/a.qcow2")
self.assertEqual(len(p.lookups), 4)
def disk(path, cache):
return (
f"<disk type='file' device='disk'><driver name='qemu' type='qcow2' cache='{cache}'/>"
f"<source file='{path}'/><target dev='vda' bus='virtio'/></disk>"
)
class DiskListsTestCase(unittest.TestCase):
"""The page lists the running and the persistent disks; both share a
path's volume, each keeps its own settings."""
def lists(self, live, config, volumes):
p = volume_proxy(volumes)
xml = {0: f"<domain><devices>{live}</devices></domain>", 3: f"<domain><devices>{config}</devices></domain>"}
p.instance.XMLDesc = lambda flags: xml[flags]
with p.cached_reads():
return p, p.get_disk_devices(), p.get_disk_devices(config=True)
def test_one_path_in_both_lists_is_looked_up_once(self):
p, live, config = self.lists(disk("/p/a.qcow2", "none"), disk("/p/a.qcow2", "writeback"), {"/p/a.qcow2"})
self.assertEqual(p.lookups, ["/p/a.qcow2"])
self.assertEqual([(d["image"], d["storage"], d["size"], d["cache"]) for d in live], [("a.qcow2", "pool", 10, "none")])
self.assertEqual([d["cache"] for d in config], ["writeback"])
self.assertIsNot(live[0], config[0])
def test_a_pending_disk_change_is_looked_up_on_its_own(self):
p, live, config = self.lists(disk("/p/a.qcow2", "none"), disk("/p/b.qcow2", "none"), {"/p/a.qcow2", "/p/b.qcow2"})
self.assertEqual(p.lookups, ["/p/a.qcow2", "/p/b.qcow2"])
self.assertEqual((live[0]["image"], config[0]["image"]), ("a.qcow2", "b.qcow2"))
def test_a_disk_outside_any_pool_shows_its_path(self):
p, live, _ = self.lists(disk("/srv/x.img", "none"), disk("/srv/x.img", "none"), set())
self.assertEqual(p.lookups, ["/srv/x.img"])
self.assertEqual((live[0]["image"], live[0]["storage"], live[0]["size"]), ("/srv/x.img", None, None))