1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00
webvirtcloud/vrtManager/tests/test_live_connection.py
catborise c55f52f025 test: each app keeps its tests in a tests package
131 test modules sat next to the code they test; instances alone had
51 of them beside views.py and utils.py. They move into a
tests/ package per app, and the JavaScript checks into
webvirtcloud/tests/js. Each non-empty tests.py becomes tests/test_views.py;
the empty generated stubs are removed.

Imports of the app's own modules become absolute, paths built from
__file__ go one level up, the live helper is instances.tests.livetest,
and coverage omits */tests/*. The suite discovers the same 960 tests as
before.
2026-10-12 15:17:30 +03:00

249 lines
10 KiB
Python

"""Live checks of bounded connection opens against real libvirt.
Runs only with TEST_LIBVIRT_HOST set (a TCP compute, see instances/tests/livetest.py);
TEST_LIBVIRT_SSH_HOST/_LOGIN add a reachable ssh compute. 192.0.2.1 is
TEST-NET-1: SYN packets to it are dropped, as for a host that went away
without a trace.
TEST_LIBVIRT_SSH_ALIASES=1 adds the ssh tests that need these entries in the
ssh configuration of the user running the tests (SSH_HOST is the ssh compute):
Host wvc-alias
HostName SSH_HOST
Host wvc-relay
HostName 127.0.0.1
Port 2222
HostKeyAlias SSH_HOST
Host wvc-silent
HostName 127.0.0.1
Port 2223
TEST_LIBVIRT_SSH_JUMP=1 adds a ProxyJump test through
Host wvc-jump
HostName SSH_HOST
ProxyJump user@another-host
"""
import os
import shutil
import socket
import subprocess
import threading
import time
import unittest
import libvirt
from vrtManager.connection import CONN_SSH, CONN_TCP, CONNECT_TIMEOUT, connection_manager
UNREACHABLE = "192.0.2.1"
KEEPALIVE_DEATH = 45 # keepalive (5, 5) closes a silent connection after ~30 s
def timed(*args):
started = time.monotonic()
try:
return connection_manager.get_connection(*args), time.monotonic() - started
except libvirt.libvirtError as err:
return err, time.monotonic() - started
def ssh_processes(host):
"""ssh client processes (exact name) whose arguments mention host."""
out = subprocess.run(["pgrep", "-a", "-x", "ssh"], capture_output=True, text=True).stdout
return [line for line in out.splitlines() if host in line]
class Relay:
"""TCP relay that can hold all traffic (frozen) and pass it again (thawed);
a frozen relay looks like a host that went silent."""
def __init__(self, target, port=0, frozen=False):
self.forwarding = threading.Event()
if not frozen:
self.forwarding.set()
self.server = socket.socket()
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server.bind(("127.0.0.1", port))
self.server.listen(5)
self.port = self.server.getsockname()[1]
self.target = target
self.closed = False
self.sockets = []
threading.Thread(target=self.accept, daemon=True).start()
def accept(self):
while True:
try:
client, _ = self.server.accept()
except OSError:
return
upstream = socket.create_connection(self.target)
self.sockets += [client, upstream]
for a, b in ((client, upstream), (upstream, client)):
threading.Thread(target=self.pump, args=(a, b), daemon=True).start()
def pump(self, src, dst):
try:
while not self.closed:
if not self.forwarding.wait(0.2):
continue
src.settimeout(0.2)
try:
data = src.recv(65536)
except socket.timeout:
continue
if not data:
break
dst.sendall(data)
except OSError:
pass
def freeze(self):
self.forwarding.clear()
time.sleep(0.5) # let a recv that was already waiting run out
def thaw(self):
self.forwarding.set()
def close(self):
"""Stop, and end the relayed connections, as a host that went away would."""
self.closed = True
self.forwarding.set()
try:
self.server.shutdown(socket.SHUT_RDWR) # wakes the accept; close alone does not
except OSError:
pass
self.server.close()
for sock in self.sockets:
sock.close()
@unittest.skipUnless(os.environ.get("TEST_LIBVIRT_HOST"), "Set TEST_LIBVIRT_HOST to run the live libvirt tests")
class LiveBoundedOpenTestCase(unittest.TestCase):
def setUp(self):
self.host = os.environ["TEST_LIBVIRT_HOST"]
self.login = os.environ.get("TEST_LIBVIRT_LOGIN", "")
self.password = os.environ.get("TEST_LIBVIRT_PASSWORD", "")
self.ssh_host = os.environ.get("TEST_LIBVIRT_SSH_HOST")
self.ssh_login = os.environ.get("TEST_LIBVIRT_SSH_LOGIN", "root")
# Opening
def test_unreachable_tcp_host_fails_within_the_timeout_then_at_once(self):
args = (UNREACHABLE, "u", "p", CONN_TCP)
first, took = timed(*args)
self.assertIsInstance(first, libvirt.libvirtError)
self.assertLess(took, CONNECT_TIMEOUT + 1)
second, took = timed(*args)
self.assertIsInstance(second, libvirt.libvirtError)
self.assertLess(took, 0.5)
@unittest.skipUnless(shutil.which("ssh"), "no ssh client")
def test_unreachable_ssh_host_fails_within_the_timeout_and_leaves_no_ssh(self):
result, took = timed(UNREACHABLE, "root", "", CONN_SSH)
self.assertIsInstance(result, libvirt.libvirtError)
self.assertLess(took, CONNECT_TIMEOUT + 1)
time.sleep(12) # the wrapper's ConnectTimeout is 10 s
self.assertEqual(ssh_processes(UNREACHABLE), [], "an ssh process outlived its connect timeout")
def test_a_daemon_that_never_answers_fails_within_the_timeout(self):
silent = socket.socket()
silent.bind(("127.0.0.1", 0))
silent.listen(5) # accepts (kernel backlog), never answers
self.addCleanup(silent.close)
result, took = timed(f"127.0.0.1:{silent.getsockname()[1]}", "u", "p", CONN_TCP)
self.assertIsInstance(result, libvirt.libvirtError)
self.assertLess(took, CONNECT_TIMEOUT + 1)
def check_slow_open_completes_later(self, args, relay):
first, _ = timed(*args)
self.assertIsInstance(first, libvirt.libvirtError)
relay.thaw()
deadline = time.monotonic() + 15
while time.monotonic() < deadline:
conn, _ = timed(*args)
if not isinstance(conn, libvirt.libvirtError):
break
time.sleep(0.5)
self.assertNotIsInstance(conn, libvirt.libvirtError)
self.assertTrue(conn.getVersion())
def test_a_slow_tcp_open_that_completes_later_is_used(self):
relay = Relay((self.host, 16509), frozen=True)
self.addCleanup(relay.close)
self.check_slow_open_completes_later((f"127.0.0.1:{relay.port}", self.login, self.password, CONN_TCP), relay)
# A connection whose host goes silent
def check_silent_host_is_noticed_and_replaced(self, args, relay):
old = connection_manager.get_connection(*args)
relay.freeze()
outcome = []
def call():
try:
outcome.append(old.getVersion())
except libvirt.libvirtError as err:
outcome.append(err)
caller = threading.Thread(target=call, daemon=True)
caller.start()
caller.join(KEEPALIVE_DEATH) # keepalive must end the call by then
if caller.is_alive():
relay.close() # cut the connection so the stuck call returns
self.fail(f"a call on a silent connection still waits after {KEEPALIVE_DEATH} s")
self.assertIsInstance(outcome[0], libvirt.libvirtError)
relay.thaw()
new, _ = timed(*args)
self.assertNotIsInstance(new, libvirt.libvirtError)
self.assertIsNot(new, old)
self.assertTrue(new.getVersion())
def test_a_silent_tcp_host_is_noticed_and_reconnected(self):
relay = Relay((self.host, 16509))
self.addCleanup(relay.close)
self.check_silent_host_is_noticed_and_replaced((f"127.0.0.1:{relay.port}", self.login, self.password, CONN_TCP), relay)
# ssh
@unittest.skipUnless(os.environ.get("TEST_LIBVIRT_SSH_HOST"), "Set TEST_LIBVIRT_SSH_HOST (and _LOGIN) for an ssh compute")
def test_a_reachable_ssh_host_connects_through_the_wrapper(self):
conn, _ = timed(self.ssh_host, self.ssh_login, "", CONN_SSH)
self.assertNotIsInstance(conn, libvirt.libvirtError)
self.assertIn("command=", conn.getURI())
self.assertTrue(conn.getVersion())
@unittest.skipUnless(os.environ.get("TEST_LIBVIRT_SSH_ALIASES"), "Set TEST_LIBVIRT_SSH_ALIASES=1 (see the module docstring)")
def test_an_ssh_config_alias_is_used(self):
conn, _ = timed("wvc-alias", self.ssh_login, "", CONN_SSH)
self.assertNotIsInstance(conn, libvirt.libvirtError)
self.assertTrue(conn.getVersion())
@unittest.skipUnless(os.environ.get("TEST_LIBVIRT_SSH_ALIASES"), "Set TEST_LIBVIRT_SSH_ALIASES=1 (see the module docstring)")
def test_an_ssh_port_that_never_answers_fails_within_the_timeout(self):
silent = socket.socket()
silent.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
silent.bind(("127.0.0.1", 2223))
silent.listen(5) # no ssh banner ever comes
self.addCleanup(silent.close)
result, took = timed("wvc-silent", self.ssh_login, "", CONN_SSH)
self.assertIsInstance(result, libvirt.libvirtError)
self.assertLess(took, CONNECT_TIMEOUT + 1)
@unittest.skipUnless(os.environ.get("TEST_LIBVIRT_SSH_ALIASES"), "Set TEST_LIBVIRT_SSH_ALIASES=1 (see the module docstring)")
def test_a_slow_ssh_open_that_completes_later_is_used(self):
relay = Relay((self.ssh_host, 22), port=2222, frozen=True)
self.addCleanup(relay.close)
self.check_slow_open_completes_later(("wvc-relay", self.ssh_login, "", CONN_SSH), relay)
@unittest.skipUnless(os.environ.get("TEST_LIBVIRT_SSH_ALIASES"), "Set TEST_LIBVIRT_SSH_ALIASES=1 (see the module docstring)")
def test_a_silent_ssh_host_is_noticed_and_reconnected(self):
relay = Relay((self.ssh_host, 22), port=2222)
self.addCleanup(relay.close)
self.check_silent_host_is_noticed_and_replaced(("wvc-relay", self.ssh_login, "", CONN_SSH), relay)
@unittest.skipUnless(os.environ.get("TEST_LIBVIRT_SSH_JUMP"), "Set TEST_LIBVIRT_SSH_JUMP=1 (see the module docstring)")
def test_ssh_through_a_jump_host(self):
conn, _ = timed("wvc-jump", self.ssh_login, "", CONN_SSH)
self.assertNotIsInstance(conn, libvirt.libvirtError)
self.assertTrue(conn.getVersion())