From fb45445492dc078211f6cb570a20050af0c006e1 Mon Sep 17 00:00:00 2001 From: catborise Date: Tue, 3 Nov 2026 00:10:53 +0300 Subject: [PATCH] fix(vrtManager): a request back from other hosts counts on its own host again A request counts on the host it connected to last. A quota check reads every host of the user's VMs, and a VM's destruction the other hosts' files, in the middle of a request that then goes on with the VM's own host through the objects it kept: it was counted on the last host it read while it used its own, so a host that does not answer could take more requests than its limit, and the last host read was charged. Both checks now give the request back to the host it counted on before. --- instances/utils.py | 4 +++- vrtManager/connection.py | 11 +++++++++++ vrtManager/tests/test_connection.py | 19 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/instances/utils.py b/instances/utils.py index 31800dd4..9b9b3b48 100644 --- a/instances/utils.py +++ b/instances/utils.py @@ -12,7 +12,7 @@ from django.utils.translation import gettext_lazy as _ from libvirt import libvirtError from logs.audit import reason, write from operations.models import Operation -from vrtManager.connection import wvmConnect +from vrtManager.connection import other_hosts, wvmConnect from vrtManager.instance import wvmInstance, wvmInstances from vrtManager.util import OperationError @@ -46,6 +46,7 @@ def get_clone_free_names(size=10): return free_names +@other_hosts() def paths_used_on_other_computes(compute, own): """The paths in own that the VMs and volumes of the panel's other computes use (paths_in_use): shared storage holds a file at the same path on every @@ -74,6 +75,7 @@ def disk_gib(disks): QUOTA_UNVERIFIED = "unverified" +@other_hosts() def check_user_quota(user, instance, cpu, memory, disk_size): """ Which quota the user would exceed by adding these amounts (instances, diff --git a/vrtManager/connection.py b/vrtManager/connection.py index a00392df..949c4f19 100644 --- a/vrtManager/connection.py +++ b/vrtManager/connection.py @@ -135,6 +135,17 @@ def _count_on(connection): _request.held = connection +@contextlib.contextmanager +def other_hosts(): + """For code that reads other hosts in the middle of a web request, which + then goes on with the host it used before, through the objects it kept: + the request counts on that host again after the block (HOST_REQUESTS).""" + held = getattr(_request, "held", None) + yield + if held is not None: + _count_on(held) + + request_started.connect(_request_started, dispatch_uid="vrtManager.connection.request_started") request_finished.connect(_request_finished, dispatch_uid="vrtManager.connection.request_finished") diff --git a/vrtManager/tests/test_connection.py b/vrtManager/tests/test_connection.py index 54940e49..27f32693 100644 --- a/vrtManager/tests/test_connection.py +++ b/vrtManager/tests/test_connection.py @@ -295,6 +295,25 @@ class HostRequestsTestCase(unittest.TestCase): self.end_requests() self.assertEqual(self.request("h") + self.request("h"), []) + def test_a_request_back_from_other_hosts_counts_on_its_own_again(self): + def run(): + request_started.send(sender=None) + self.manager.get_connection("h", "u", "p", CONN_TCP) + with connection.other_hosts(): + self.manager.get_connection("g", "u", "p", CONN_TCP) + back.set() + self.done.wait(5) + request_finished.send(sender=None) + + back = threading.Event() + thread = threading.Thread(target=run) + thread.start() + self.threads.append(thread) + back.wait(5) + # it holds h again (one of two) and left g + self.assertEqual(self.request("h") + self.request("g") + self.request("g"), []) + self.assertEqual(len(self.request("h")), 1) + def test_threads_outside_a_request_are_not_counted(self): self.assertEqual(self.request("h") + self.request("h"), []) self.manager.get_connection("h", "u", "p", CONN_TCP)