blob: b56bbc43af110ef5c349b442256af6a8cb056802 [file] [edit]
############################################################################
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
############################################################################
"""An absent GPU is auto-drained with config_overrides on.
drain_absent_gres keeps the node up. config_overrides is on as well, and the
per-device topology must still be used, so an exclusive job gets only the
GPUs that are present. test_190_15 covers the same with it off.
"""
import re
import pytest
import atf
pytestmark = pytest.mark.slow
NODE = "node1"
GPU_A = "GPU-aaaaaaaa-0000-0000-0000-000000000000"
GPU_B = "GPU-bbbbbbbb-1111-1111-1111-111111111111"
GPU_C = "GPU-cccccccc-2222-2222-2222-222222222222"
NOT_FOUND = "not_found"
@pytest.fixture(scope="module", autouse=True)
def setup():
"""Three a100s, with config_overrides on as well as drain_absent_gres."""
atf.require_version(
(26, 11),
component="bin/scontrol",
reason="Ticket 25180: SlurmctldParameters=drain_absent_gres added in 26.11",
)
atf.require_version(
(26, 11),
component="sbin/slurmd",
reason="Ticket 25180: GRES device slots keyed by UUID added in 26.11",
)
atf.require_config_parameter("SelectType", "select/cons_tres")
atf.require_config_parameter("SelectTypeParameters", "CR_CPU")
atf.require_config_parameter_includes("GresTypes", "gpu")
atf.require_config_parameter_includes("SlurmctldParameters", "drain_absent_gres")
atf.require_config_parameter_includes("SlurmdParameters", "config_overrides")
for tty in range(3):
atf.require_tty(tty)
atf.require_config_file("gres.conf", "Autodetect=full\n")
atf.require_config_file(
"fake_gpus.conf", atf.fake_gpus_conf((0, GPU_A), (1, GPU_B), (2, GPU_C))
)
atf.require_nodes(1, [("Gres", "gpu:a100:3"), ("CPUs", 2)])
atf.require_slurm_running()
def test_absent_gpu_auto_drains_with_config_overrides():
"""An absent GPU is auto-drained, and an exclusive job gets only the two
present GPUs.
The node stays up and DEGRADED. The per-device topology is kept, so the
job is counted against the GPUs that are there.
"""
atf.restart_slurmd_with_fake_gpus(NODE, (0, GPU_A), (2, GPU_C))
for _ in atf.timer(fatal=True):
detail = atf.get_node_detail(NODE)
if bool(re.search(rf"gpu\(IDX:[^)]*\)\([^)]*{NOT_FOUND}", detail or "")):
break
state = atf.get_node_field(NODE, "State")
assert "INVAL" not in state, atf.get_node_detail(NODE)
assert "DOWN" not in state, atf.get_node_detail(NODE)
assert "DEGRADED" in state, atf.get_node_detail(NODE)
job_id = atf.submit_job_sbatch(
f"-w {NODE} --exclusive --wrap 'sleep infinity'", fatal=True
)
assert atf.wait_for_job_state(
job_id, "RUNNING"
), f"exclusive job did not run with an absent GPU: {atf.get_node_detail(NODE)}"
detail = atf.run_command_output(
f"scontrol -d show job {job_id}", fatal=True, quiet=True
)
assert re.search(r"gpu:a100:2\b", detail), f"job lost its GPUs: {detail}"
assert not re.search(
r"gpu:a100:3\b", detail
), f"job was given the absent GPU: {detail}"