blob: 81b0b6f44ab9057861de84693e651f0ec3fa2b9a [file] [edit]
############################################################################
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
############################################################################
"""Test that sacct keeps the requested reservation in ReqReservation."""
import pytest
import atf
# SETUP
@pytest.fixture(scope="module", autouse=True)
def setup():
atf.require_version(
(25, 5),
"bin/sacct",
reason="Issue 50285: sacct ReqReservation format field added in 25.05",
)
atf.require_accounting(modify=True)
# A job released from an ended reservation waits for a scheduling cycle.
atf.require_config_parameter_includes("SchedulerParameters", ("sched_interval", 1))
atf.require_config_parameter_includes("SchedulerParameters", ("bf_interval", 1))
atf.require_slurm_running()
@pytest.fixture(scope="module")
def resv_node():
return next(iter(atf.get_nodes(fatal=True)))
@pytest.fixture(scope="function", autouse=True)
def cleanup_resv():
yield
delete_old_resv()
# HELPER FUNCTIONS
def create_resv(node, end, flags=None):
"""Create a reservation to use for testing"""
atf.run_command(
f"scontrol create reservation reservationname=test_resv "
f"{f'flags={flags} ' if flags else ''}"
f"user={atf.properties['test-user']} start=now end=now+{end} "
f"nodes={node}",
user=atf.properties["slurm-user"],
fatal=True,
)
def delete_old_resv():
"""Delete the reservation if it's still there"""
for _ in atf.timer(fatal=True):
if "test_resv" not in atf.get_reservations(fatal=True):
break
atf.run_command(
"scontrol delete reservation test_resv",
user=atf.properties["slurm-user"],
)
def get_resv_fields(job_id, state):
"""Return the Reservation and ReqReservation sacct fields of the job once
sacct shows the job in the given state"""
for _ in atf.timer(fatal=True):
fields = atf.run_command_output(
f"sacct -j {job_id} -X -n -P --format=State,Reservation,ReqReservation",
fatal=True,
).strip()
if fields.startswith(f"{state}|"):
break
return fields.split("|")[1:]
# TESTS
def test_reqreservation_after_resv_deleted(resv_node):
"""Checks that ReqReservation outlives the reservation the job ran in"""
create_resv(resv_node, 60)
job_id = atf.submit_job_sbatch(
"--reservation=test_resv -t1 --wrap='true'", fatal=True
)
atf.wait_for_job_state(job_id, "COMPLETED", fatal=True)
delete_old_resv()
_, req_resv = get_resv_fields(job_id, "COMPLETED")
assert req_resv == "test_resv", "test_resv was not the ReqReservation"
def test_reqreservation_ran_outside_resv(resv_node):
"""Checks ReqReservation of a job that ran after its reservation ended"""
create_resv(resv_node, 2, "NO_HOLD_JOBS_AFTER_END")
job_id = atf.submit_job_sbatch(
"--reservation=test_resv --begin=now+4 -t1 --wrap='true'", fatal=True
)
atf.wait_for_job_state(job_id, "COMPLETED", fatal=True)
resv, req_resv = get_resv_fields(job_id, "COMPLETED")
assert resv == "", "The job should not have run in test_resv"
assert req_resv == "test_resv", "test_resv was not the ReqReservation"
def test_reqreservation_held_after_resv_end(resv_node):
"""Checks ReqReservation of a job held because its reservation ended"""
create_resv(resv_node, 2)
job_id = atf.submit_job_sbatch(
"--reservation=test_resv --begin=now+4 -t1 --wrap='true'", fatal=True
)
atf.wait_for_job_state(job_id, "RESV_DEL_HOLD", fatal=True)
_, req_resv = get_resv_fields(job_id, "PENDING")
assert req_resv == "test_resv", "test_resv was not the ReqReservation"