mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-11 04: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:
parent
95b4418c96
commit
439f24041e
6 changed files with 278 additions and 52 deletions
|
|
@ -1,5 +1,6 @@
|
|||
"""The VM page reads each kind of domain XML and the host capabilities once,
|
||||
through the real getters, on libvirt's test driver."""
|
||||
"""The VM page reads each kind of domain XML, the VM's state, the host's
|
||||
capabilities and info, and each disk's volume once, through the real getters,
|
||||
on libvirt's test driver."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
|
|
@ -10,17 +11,22 @@ from django.test import TestCase
|
|||
from django.urls import reverse
|
||||
from instances.models import Instance
|
||||
|
||||
# a VM with a VNC console, on libvirt's test driver
|
||||
DOMAIN = """<domain type='test'><name>read-cache-vm</name><memory unit='MiB'>128</memory><vcpu>1</vcpu>
|
||||
# a VM with a disk and a VNC console, on libvirt's test driver
|
||||
DOMAIN = """<domain type='test'><name>read-cache-vm</name><uuid>0b9f6a52-6d0e-4c1f-9a51-2f3c1d7e8a40</uuid><memory unit='MiB'>128</memory><vcpu>1</vcpu>
|
||||
<os><type arch='i686'>hvm</type></os>
|
||||
<devices><graphics type='vnc' port='-1' listen='127.0.0.1'><listen type='address' address='127.0.0.1'/></graphics></devices>
|
||||
<devices><disk type='file' device='disk'><source file='/default-pool/read-cache.img'/><target dev='vda' bus='virtio'/></disk>
|
||||
<graphics type='vnc' port='-1' listen='127.0.0.1'><listen type='address' address='127.0.0.1'/></graphics></devices>
|
||||
</domain>"""
|
||||
|
||||
|
||||
class DetailReadsTests(TestCase):
|
||||
def setUp(self):
|
||||
self.conn = libvirt.open("test:///default") # each open is a fresh, private test host
|
||||
self.conn = libvirt.open("test:///default")
|
||||
self.addCleanup(self.conn.close)
|
||||
# the test host lives on while a libvirt object still refers to it
|
||||
pool = self.conn.storagePoolLookupByName("default-pool")
|
||||
if "read-cache.img" not in pool.listVolumes():
|
||||
pool.createXML("<volume><name>read-cache.img</name><capacity>1048576</capacity></volume>", 0)
|
||||
domain = self.conn.defineXML(DOMAIN)
|
||||
self.compute = Compute.objects.create(name="testdrv", hostname="localhost", login="", password="", type=4)
|
||||
self.vm = Instance.objects.create(compute=self.compute, name=domain.name(), uuid=domain.UUIDString())
|
||||
|
|
@ -56,6 +62,43 @@ class DetailReadsTests(TestCase):
|
|||
self.assertEqual(sorted(xml_reads), sorted(set(xml_reads)), "an XML read twice")
|
||||
self.assertEqual(len(cap_reads), 1)
|
||||
|
||||
def test_the_state_host_info_and_disk_volume_are_read_once(self):
|
||||
counted = (
|
||||
(libvirt.virDomain, "info"),
|
||||
(libvirt.virConnect, "getInfo"),
|
||||
(libvirt.virConnect, "getDomainCapabilities"),
|
||||
(libvirt.virConnect, "storageVolLookupByPath"),
|
||||
(libvirt.virStorageVol, "info"),
|
||||
)
|
||||
calls = {}
|
||||
|
||||
def counter(cls, name):
|
||||
real = getattr(cls, name)
|
||||
|
||||
def call(*args, **kwargs):
|
||||
key = f"{cls.__name__}.{name}"
|
||||
calls[key] = calls.get(key, 0) + 1
|
||||
return real(*args, **kwargs)
|
||||
|
||||
return call
|
||||
|
||||
patches = [patch.object(cls, name, counter(cls, name)) for cls, name in counted]
|
||||
for p in patches:
|
||||
p.start()
|
||||
self.addCleanup(p.stop)
|
||||
response, _, _ = self.render()
|
||||
self.assertContains(response, "read-cache.img")
|
||||
self.assertEqual(
|
||||
calls,
|
||||
{
|
||||
"virDomain.info": 1,
|
||||
"virConnect.getInfo": 1,
|
||||
"virConnect.getDomainCapabilities": 1,
|
||||
"virConnect.storageVolLookupByPath": 1,
|
||||
"virStorageVol.info": 1,
|
||||
},
|
||||
)
|
||||
|
||||
def test_a_change_after_the_page_reads_fresh_xml(self):
|
||||
# outside the page the getters read libvirt each time, as changes need
|
||||
with patch("vrtManager.connection.connection_manager.get_connection", return_value=self.conn):
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
"""drbd_status runs ssh with the compute's login@hostname; a compute whose
|
||||
values would inject an ssh option (an existing or bypassed row) is skipped."""
|
||||
values would inject an ssh option (an existing or bypassed row) is skipped,
|
||||
and a compute that does not answer in time leaves the status unknown."""
|
||||
|
||||
import subprocess
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
from instances.views import _valid_ssh_target
|
||||
from instances.views import DRBD_STATUS_TIMEOUT, _valid_ssh_target, drbd_status
|
||||
|
||||
|
||||
class DrbdTargetTestCase(unittest.TestCase):
|
||||
|
|
@ -15,3 +18,11 @@ class DrbdTargetTestCase(unittest.TestCase):
|
|||
for login, hostname in (("-oProxyCommand=x", "192.0.2.1"), ("root", "-x"), ("root", "a b")):
|
||||
with self.subTest(login=login, hostname=hostname):
|
||||
self.assertFalse(_valid_ssh_target(SimpleNamespace(login=login, hostname=hostname)))
|
||||
|
||||
def test_a_compute_that_does_not_answer_leaves_the_status_unknown(self):
|
||||
vm = SimpleNamespace(name="vm1", compute=SimpleNamespace(type=2, login="root", hostname="192.0.2.1"))
|
||||
with patch("instances.views.get_instance", return_value=vm), patch(
|
||||
"instances.views.subprocess.run", side_effect=subprocess.TimeoutExpired("ssh", DRBD_STATUS_TIMEOUT)
|
||||
) as run:
|
||||
self.assertEqual(drbd_status(SimpleNamespace(user=None), 1), "None DRBD")
|
||||
self.assertEqual(run.call_args.kwargs["timeout"], DRBD_STATUS_TIMEOUT)
|
||||
|
|
|
|||
|
|
@ -52,6 +52,9 @@ from . import utils
|
|||
from .forms import ConsoleForm, FlavorForm, NewVMForm
|
||||
from .models import Flavor
|
||||
|
||||
# the VM page waits this long (seconds) for the compute's drbdadm status
|
||||
DRBD_STATUS_TIMEOUT = 10
|
||||
|
||||
|
||||
def index(request):
|
||||
instances = None
|
||||
|
|
@ -224,11 +227,15 @@ def drbd_status(request, pk):
|
|||
|
||||
if instance.compute.type == 2 and _valid_ssh_target(instance.compute):
|
||||
conn = instance.compute.login + "@" + instance.compute.hostname
|
||||
remoteDrbdStatus = subprocess.run(
|
||||
["ssh", "--", conn, "sudo", "/usr/sbin/drbdadm", "status"],
|
||||
stdout=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
try:
|
||||
remoteDrbdStatus = subprocess.run(
|
||||
["ssh", "--", conn, "sudo", "/usr/sbin/drbdadm", "status"],
|
||||
stdout=subprocess.PIPE,
|
||||
text=True,
|
||||
timeout=DRBD_STATUS_TIMEOUT,
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
return result
|
||||
|
||||
if remoteDrbdStatus.stdout:
|
||||
instanceFindDrbd = re.compile(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue