1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-10 20:26:48 +00:00

feat(vrtManager): a host that does not answer holds two requests of a worker

With threaded workers a request waiting on a stuck host holds one
thread, but each new request to that host (a VM list's poll every 5 s,
an F5) held another until the threads ran out.

In a process at most two web requests now use one host at a time,
counted by its name whatever the login. Another waits up to the connect
timeout (5 s) for one of them to end, and is then refused as the host
not answering: VM lists show the host as not reachable, pages show the
connection error. A request counts on the host it connected to last,
until it ends, so one that reads several hosts in turn holds none it
has left. Threads outside a request (the job worker's, the consoles')
are not counted: a job holds its host for as long as it runs.
This commit is contained in:
catborise 2026-11-02 21:30:56 +03:00
parent 6709681508
commit 2d47f87dcb
2 changed files with 138 additions and 0 deletions

View file

@ -8,6 +8,7 @@ import unittest
from unittest.mock import MagicMock, patch
from django.conf import settings
from django.core.signals import request_finished, request_started
if not settings.configured:
settings.configure(MAC_OUI="52:54:10")
@ -214,6 +215,91 @@ class BoundedOpenTestCase(unittest.TestCase):
self.assertIn("ConnectTimeout", script.read())
class HostRequestsTestCase(unittest.TestCase):
"""At most HOST_REQUESTS web requests of a process use one host at a
time; another waits CONNECT_TIMEOUT and is refused."""
def setUp(self):
for name, value in (("wvmEventLoop", MagicMock()), ("CONNECT_TIMEOUT", 0.2)):
patcher = patch(f"vrtManager.connection.{name}", value)
patcher.start()
self.addCleanup(patcher.stop)
patcher = patch("vrtManager.connection.libvirt.openAuth", side_effect=lambda *args: alive_connection())
patcher.start()
self.addCleanup(patcher.stop)
self.manager = wvmConnectionManager()
self.done = threading.Event()
self.threads = []
self.addCleanup(self.end_requests)
def end_requests(self):
self.done.set()
for thread in self.threads:
thread.join(5)
self.threads, self.done = [], threading.Event()
def request(self, *hosts, login="u"):
"""A web request on its own thread: it connects to hosts in turn and
ends when self.done is set. Returns its errors, filled once connected."""
errors, connected, done = [], threading.Event(), self.done
def run():
request_started.send(sender=None)
try:
for host in hosts:
self.manager.get_connection(host, login, "p", CONN_TCP)
except libvirt.libvirtError as err:
errors.append(str(err))
connected.set()
done.wait(5)
request_finished.send(sender=None)
thread = threading.Thread(target=run)
thread.start()
self.threads.append(thread)
connected.wait(5)
return errors
def test_a_third_request_to_a_host_is_refused_and_other_hosts_answer(self):
self.assertEqual(self.request("h") + self.request("h"), [])
started = time.monotonic()
self.assertEqual(self.request("h"), ["Connection Failed: h does not answer: 2 requests still wait for it"])
self.assertGreaterEqual(time.monotonic() - started, 0.2)
self.assertEqual(self.request("g"), [])
def test_a_host_counts_its_requests_whatever_the_login(self):
# two records of one host (another login, or credentials changed meanwhile)
self.assertEqual(self.request("h") + self.request("h", login="other"), [])
self.assertEqual(len(self.request("h", login="third")), 1)
def test_a_request_without_request_finished_leaves_its_host_at_the_next_one(self):
self.assertEqual(self.request("h"), [])
request_started.send(sender=None) # this thread: a request that never got request_finished
try:
self.manager.get_connection("h", "u", "p", CONN_TCP)
request_started.send(sender=None) # its next request on the thread
self.assertEqual(self.request("h"), [])
finally:
request_finished.send(sender=None)
def test_a_request_counts_once_on_a_host(self):
self.assertEqual(self.request("h", "h", "h") + self.request("h"), [])
self.assertEqual(len(self.request("h")), 1)
def test_a_request_counts_on_the_host_it_connected_to_last(self):
self.assertEqual(self.request("h", "g") + self.request("h") + self.request("h"), [])
self.assertEqual(len(self.request("g", "h")), 1)
def test_an_ended_request_lets_the_next_one_in(self):
self.assertEqual(self.request("h") + self.request("h"), [])
self.end_requests()
self.assertEqual(self.request("h") + self.request("h"), [])
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)
class EventLoopTestCase(unittest.TestCase):
"""The libvirt event loop thread starts with the first connection of a
process, not at import: novncd forks a child per console after importing