From 337063bf7b65770e59b465dc0b1eafb3499d5ad2 Mon Sep 17 00:00:00 2001 From: catborise Date: Wed, 4 Nov 2026 23:58:36 +0300 Subject: [PATCH] perf(instances): a VM list reads only the VMs it shows #394 read each host's states in one call and every domain's XML once, for the title, memory and vCPUs. A user's list shows only their own VMs, but it read the XML of every VM on those hosts: a user with two VMs on a host of 300 made about 301 libvirt calls a list load, where the old per-VM reads made 6. get_domain_rows takes the UUIDs the list shows and reads only their XML; the states still come in one call. The capacity reads every domain, as before. --- instances/tests/test_list_rpc.py | 16 ++++++++++++++-- instances/tests/test_list_unreachable.py | 2 +- instances/tests/test_states.py | 2 +- instances/utils.py | 6 ++++-- vrtManager/connection.py | 13 ++++++++----- 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/instances/tests/test_list_rpc.py b/instances/tests/test_list_rpc.py index 88aa2df4..bf1d4c8b 100644 --- a/instances/tests/test_list_rpc.py +++ b/instances/tests/test_list_rpc.py @@ -65,8 +65,8 @@ class ListReadTests(TestCase): def assert_read_once_a_host(self, url): response, calls = self.get(url) - # every domain of the host once (the test driver's own, and other tests', too) - self.assertEqual(calls, {"XMLDesc": len(self.conn.listAllDomains())}) + # each VM the list shows once; the host's other domains (the test driver's own) not at all + self.assertEqual(calls, {"XMLDesc": len(VMS)}) self.assertContains(response, "2048 MiB", count=len(VMS)) self.assertContains(response, "Active") self.assertContains(response, "vm-a title") @@ -81,3 +81,15 @@ class ListReadTests(TestCase): def test_compute_instances(self): self.assert_read_once_a_host(reverse("instances", args=[self.compute.id])) + + def test_a_user_s_list_reads_only_their_vms(self): + from accounts.models import UserInstance + + user = get_user_model().objects.create_user("rpc-user", password="pwd") + UserInstance.objects.create(user=user, instance=Instance.objects.get(name="vm-a")) + self.client.force_login(user) + response, calls = self.get(reverse("instances:index")) + # the other VMs of the host, the test driver's own and other tests' included, are not read + self.assertEqual(calls, {"XMLDesc": 1}) + self.assertContains(response, "vm-a title") + self.assertNotContains(response, "vm-b") diff --git a/instances/tests/test_list_unreachable.py b/instances/tests/test_list_unreachable.py index 6d831ac2..669d6c03 100644 --- a/instances/tests/test_list_unreachable.py +++ b/instances/tests/test_list_unreachable.py @@ -31,7 +31,7 @@ class UnreachableListTests(TestCase): or up with the error its listing raises.""" up = rows is not None - def domain_rows(): + def domain_rows(uuids=None): if isinstance(rows, Exception): raise rows return rows diff --git a/instances/tests/test_states.py b/instances/tests/test_states.py index 41781530..7a90efdd 100644 --- a/instances/tests/test_states.py +++ b/instances/tests/test_states.py @@ -58,7 +58,7 @@ class StateTests(TestCase): self.client.force_login(user) props = {"status": True, "connection_error": None, "cpu_count": 1, "ram_size": 1, "ram_usage": 0, - "proxy": SimpleNamespace(get_domain_rows=lambda: rows)} + "proxy": SimpleNamespace(get_domain_rows=lambda uuids=None: rows)} patches = [patch.object(Compute, k, new_callable=PropertyMock, return_value=v) for k, v in props.items()] patches += [patch("instances.views.utils.refr"), patch("computes.views.utils.refresh_instance_database")] for p in patches: diff --git a/instances/utils.py b/instances/utils.py index 2ce3f5af..0cbc4567 100644 --- a/instances/utils.py +++ b/instances/utils.py @@ -356,10 +356,12 @@ def read_list_rows(instances): """Each VM's info and title for a VM list, read once a host (get_domain_rows), not three calls a VM; a VM whose host or whose domain cannot be read has no info: the list shows it unknown.""" - rows = {} + rows, shown = {}, {} + for inst in instances: + shown.setdefault(inst.compute_id, set()).add(inst.uuid) for compute in {inst.compute_id: inst.compute for inst in instances}.values(): try: - rows[compute.pk] = compute.proxy.get_domain_rows() if compute.status is True else {} + rows[compute.pk] = compute.proxy.get_domain_rows(shown[compute.pk]) if compute.status is True else {} except libvirtError: rows[compute.pk] = {} for inst in instances: diff --git a/vrtManager/connection.py b/vrtManager/connection.py index 311343f9..91032870 100644 --- a/vrtManager/connection.py +++ b/vrtManager/connection.py @@ -1098,13 +1098,16 @@ class wvmConnect(object): return {dom.UUIDString(): [dom.name(), stats["state.state"]] for dom, stats in self.wvm.getAllDomainStats(libvirt.VIR_DOMAIN_STATS_STATE)} - def get_domain_rows(self): - """{uuid: (info, title)} of every domain, as the VM lists show them: - info in virDomainGetInfo's shape (state, max memory KiB, memory KiB, - vCPUs), the states in one call and the rest from each domain's live - XML, which the title needs anyway. A domain gone meanwhile is left out.""" + def get_domain_rows(self, uuids=None): + """{uuid: (info, title)} of every domain, or of those in uuids, as the + VM lists show them: info in virDomainGetInfo's shape (state, max + memory KiB, memory KiB, vCPUs), the states in one call and the rest + from each domain's live XML, which the title needs anyway. A domain + gone meanwhile is left out.""" rows = {} for dom, stats in self.wvm.getAllDomainStats(libvirt.VIR_DOMAIN_STATS_STATE): + if uuids is not None and dom.UUIDString() not in uuids: + continue # a VM the list does not show: its XML is not read try: doc = etree.fromstring(dom.XMLDesc(0)) except libvirtError: