1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00
webvirtcloud/instances/test_list_rpc.py
catborise 7e08224330 perf(instances): instance lists read a VM's info once per row
The VM rows of the instance lists used instance.proxy.instance.info in
every state check, the vCPU column and the action buttons. A template
calls the method on each lookup, so every row made 7 virDomainGetInfo
RPCs (8 on a compute's instances page), and the memory column read the
domain XML once more.

- Instance.info reads virDomainGetInfo once per request; the lists and
  instance_actions.html use it.
- The memory column of both index views comes from the same info
  (current memory) instead of the domain XML. The value is the same:
  checked against currentMemory for every running and shut-off VM of
  the test hosts.

A row now makes one info call and one XML read (the title). The new
test counts the calls in the grouped and non-grouped index and on the
compute instances page; it fails on the previous code with 7 and 8
calls.
2026-10-05 10:17:47 +03:00

86 lines
3.5 KiB
Python

"""Instance lists ask libvirt for a VM's info once per row."""
from types import SimpleNamespace
from unittest.mock import PropertyMock, patch
from appsettings.models import AppSettings
from computes.models import Compute
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse
from instances.models import Instance
# virDomainGetInfo: state, max memory KiB, memory KiB, vCPUs, CPU time
INFO = [1, 4194304, 2097152, 3, 0]
class FakeDomain:
# a plain class: Django templates do not call MagicMock attributes
def __init__(self):
self.info_calls = 0
self.xml_calls = 0
def info(self):
self.info_calls += 1
return INFO
def XMLDesc(self, flags=0):
self.xml_calls += 1
return "<domain><title></title><currentMemory>2097152</currentMemory></domain>"
class ListInfoCallTests(TestCase):
def setUp(self):
self.client.force_login(
get_user_model().objects.create_superuser(username="rpc-admin", password="pwd", email="rpc@example.com")
)
self.compute = Compute.objects.create(name="rpc-compute", hostname="localhost", type=4, login="", password="")
Instance.objects.create(compute=self.compute, name="vm-a", uuid="11111111-1111-1111-1111-111111111111")
Instance.objects.create(compute=self.compute, name="vm-b", uuid="22222222-2222-2222-2222-222222222222")
self.domains = []
def proxy(self, *args, **kwargs):
domain = FakeDomain()
self.domains.append(domain)
return SimpleNamespace(
instance=domain,
get_uuid=lambda: "uuid",
get_title=lambda: domain.XMLDesc() and "",
get_cur_memory=lambda: int(domain.XMLDesc().split(">")[4].split("<")[0]) // 1024,
)
def get(self, url):
compute_patches = {"status": True, "cpu_count": 1, "ram_size": 1024, "ram_usage": 0, "cpu_usage": 0}
with patch("instances.views.utils.refr"), patch("computes.views.utils.refresh_instance_database"), patch(
"instances.models.wvmInstance", side_effect=self.proxy
):
patches = [patch.object(Compute, k, new_callable=PropertyMock, return_value=v) for k, v in compute_patches.items()]
for p in patches:
p.start()
try:
response = self.client.get(url)
finally:
for p in patches:
p.stop()
self.assertEqual(response.status_code, 200)
return response
def assert_one_info_per_vm(self, response, memory="2048 MB"):
self.assertEqual(len(self.domains), 2, "one proxy per VM row")
for domain in self.domains:
self.assertEqual(domain.info_calls, 1)
self.assertEqual(domain.xml_calls, 1, "only the title reads the XML")
self.assertContains(response, memory, count=2)
self.assertContains(response, "Active")
def test_nongrouped_index(self):
AppSettings.objects.filter(key="VIEW_INSTANCES_LIST_STYLE").update(value="nongrouped")
self.assert_one_info_per_vm(self.get(reverse("instances:index")))
def test_grouped_index(self):
AppSettings.objects.filter(key="VIEW_INSTANCES_LIST_STYLE").update(value="grouped")
self.assert_one_info_per_vm(self.get(reverse("instances:index")))
def test_compute_instances(self):
# this page shows the maximum memory, as before
self.assert_one_info_per_vm(self.get(reverse("instances", args=[self.compute.id])), memory="4096 MiB")