blob: 94a6e31378f47f031a4c137b74aa6142bfd15ca7 [file] [edit]
############################################################################
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
############################################################################
"""Node reboot Reason survives a later job allocation (ticket 25784).
make_node_alloc() unconditionally cleared node_ptr->reason whenever a job
was allocated to a node, even while REBOOT_REQUESTED/REBOOT_ISSUED/DRAIN was
still pending. On a shared (Exclusive=NO) node, any job landing on the node
after `scontrol reboot ... reason=...` silently erased the reason, while the
REBOOT_REQUESTED state flag itself remained set -- so `scontrol show node`
stopped reporting why the node was rebooting, even though the reboot was
still outstanding.
Requires: a node with >=2 CPUs so a second job can land on the same node
while the first job (and the pending reboot) are still active, and a
RebootProgram so `scontrol reboot` is accepted.
"""
import pytest
import atf
node_name = None
@pytest.fixture(scope="module", autouse=True)
def setup():
global node_name
atf.require_version(
(26, 5, 6),
"sbin/slurmctld",
reason="Ticket 25784: node reboot Reason cleared by make_node_alloc()"
" on any subsequent job allocation to the node",
)
atf.require_config_parameter("SelectType", "select/cons_tres")
atf.require_config_parameter("SelectTypeParameters", "CR_CPU")
node_name = atf.require_nodes(1, [("CPUs", 2), ("RealMemory", 20)])[0]
atf.require_config_parameter("RebootProgram", "/bin/true")
atf.require_slurm_running()
def test_reboot_reason_survives_second_job_allocation():
"""Reason set by `scontrol reboot ... reason=` must survive a second
job landing on the same (shared) node while the reboot is pending."""
# Job 1 takes half the node, leaving the other CPU schedulable for job 2.
job_id_1 = atf.submit_job_sbatch(
f"-w {node_name} -n1 --mem=5 --time=10 --job-name=test_141_12_job1 "
"--wrap 'sleep infinity'",
fatal=True,
)
atf.wait_for_job_state(job_id_1, "RUNNING", fatal=True)
# Request a reboot with a reason while the node is still in use.
atf.run_command(
f"scontrol reboot nextstate=down reason=UPDATE {node_name}",
user=atf.properties["slurm-user"],
fatal=True,
)
atf.wait_for_node_state(node_name, "REBOOT_REQUESTED", fatal=True)
reason_before = atf.get_node_parameter(node_name, "reason")
assert reason_before and "UPDATE" in reason_before, (
"Reason should be UPDATE immediately after the reboot request, "
f"got {reason_before!r}"
)
# Job 2 lands on the node's remaining CPU while the reboot is pending --
# this is the allocation that used to wipe the reason via
# make_node_alloc().
job_id_2 = atf.submit_job_sbatch(
f"-w {node_name} -n1 --mem=5 --time=10 --job-name=test_141_12_job2 "
"--wrap 'sleep infinity'",
fatal=True,
)
atf.wait_for_job_state(job_id_2, "RUNNING", fatal=True)
node_state = atf.get_node_parameter(node_name, "state")
assert "REBOOT_REQUESTED" in node_state, (
"Node should still show REBOOT_REQUESTED after the second job "
f"starts, got State={node_state!r}"
)
reason_after = atf.get_node_parameter(node_name, "reason")
assert reason_after and "UPDATE" in reason_after, (
"Reason should still be UPDATE after a second job is allocated to "
f"the node while a reboot is pending (this is ticket 25784), got "
f"{reason_after!r}"
)