blob: 5ef09e4af0642c1571a3d747af143910def89419 [file] [edit]
############################################################################
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
############################################################################
"""Test slurmdbd TLS negotiation timeouts."""
import socket
import ssl
import pytest
import atf
# Scheduling and teardown slack.
GRACE_SECONDS = 15
# Do not auto-configure TLS: it needs certificate setup.
pytestmark = pytest.mark.skipif(
not atf.get_config_parameter(
"TLSType", default=None, live=False, source="slurmdbd", quiet=True
),
reason="Issue 51066: requires TLSType in slurmdbd.conf and slurm.conf",
)
@pytest.fixture(scope="module", autouse=True)
def setup():
"""Require TLS and accounting."""
atf.require_tls()
atf.require_accounting()
atf.require_slurm_running()
@pytest.fixture(scope="module")
def dbd_config():
"""Use slurmdbd's config, not slurmctld's."""
return {key.lower(): value for key, value in atf.get_config(source="dbd").items()}
@pytest.fixture(scope="module")
def dbd_address(dbd_config):
return (
dbd_config.get("dbdaddr") or dbd_config.get("dbdhost", "localhost"),
int(dbd_config.get("dbdport", 6819)),
)
@pytest.fixture(scope="module")
def message_timeout(dbd_config):
return int(str(dbd_config.get("messagetimeout", 10)).split()[0])
def _peer_closed(sock, wait_seconds):
"""Return whether the peer closes the socket within wait_seconds."""
sock.settimeout(wait_seconds)
try:
return sock.recv(1) == b""
except socket.timeout:
return False
except ConnectionResetError:
return True
@pytest.mark.parametrize("partial_hello", [False, True])
def test_stalled_tls_negotiation_times_out(dbd_address, message_timeout, partial_hello):
"""Time out silent peers and partial ClientHello messages."""
with socket.create_connection(
dbd_address, timeout=message_timeout + GRACE_SECONDS
) as sock:
if partial_hello:
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
outgoing = ssl.MemoryBIO()
client = context.wrap_bio(
ssl.MemoryBIO(), outgoing, server_hostname=dbd_address[0]
)
with pytest.raises(ssl.SSLWantReadError):
client.do_handshake()
hello = outgoing.read()
assert len(hello) > 6, "TLS did not produce a complete ClientHello"
# TLS record header plus one payload byte.
sock.sendall(hello[:6])
assert _peer_closed(
sock, message_timeout + GRACE_SECONDS
), "slurmdbd did not close a stalled TLS negotiation"