blob: 2b44825a4c5ad82232e6f2dab30c05317a0770db [file] [edit]
############################################################################
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
############################################################################
"""Test that stepmgr releases a step that fails to launch.
Under enable_stepmgr a step that fails to launch must not leave its
resources held, so a following step in the same allocation can run.
"""
import re
import pytest
import atf
@pytest.fixture(scope="module", autouse=True)
def setup():
atf.require_version(
(26, 11),
component="bin/srun",
reason="Issue 51229: srun completes unlaunched steps on the step manager",
)
atf.require_nodes(1, [("RealMemory", 100)])
atf.require_config_parameter("SelectType", "select/cons_tres")
atf.require_config_parameter("SelectTypeParameters", "CR_Core_Memory")
atf.require_config_parameter_includes("SlurmctldParameters", "enable_stepmgr")
atf.require_config_parameter_includes("PrologFlags", "Contain")
atf.require_config_parameter_includes("TaskPlugin", "task/affinity")
atf.require_config_parameter("JobAcctGatherFrequency", ["5", "task=5"])
atf.require_slurm_running()
def run_failing_then_good_step(name, bad_srun_args, extra_sbatch_args=""):
"""Run a step that fails to launch, then a good one, in one allocation.
Without --exact the first step takes all of the job's resources on the
node, so the second step can only start once the failed one is released.
Args:
name (string): Unique basename for the script and output files.
bad_srun_args (string): srun arguments that make the first step fail
to launch.
extra_sbatch_args (string): Additional sbatch arguments, such as a
memory limit.
Returns:
Tuple (rc1, rc2, stderr1) with the exit codes of the failing and the
following step, and the stderr of the failing step.
"""
script = f"{name}.sh"
output = f"{name}.out"
atf.make_bash_script(
script,
f"""\
srun -n1 {bad_srun_args} true 2> {name}.err
echo "rc1=$?"
timeout 20 srun -n1 true
echo "rc2=$?"
""",
)
job_id = atf.submit_job_sbatch(
f"-n1 {extra_sbatch_args} --output={output} {script}", fatal=True
)
atf.wait_for_job_state(job_id, "DONE", fatal=True)
atf.wait_for_file(output, fatal=True)
result = atf.run_command_output(f"cat {output}", fatal=True)
rc1 = re.search(r"rc1=(\d+)", result)
rc2 = re.search(r"rc2=(\d+)", result)
assert rc1 and rc2, f"Did not find both step results in output: {result!r}"
stderr1 = atf.run_command_output(f"cat {name}.err", fatal=True)
return int(rc1.group(1)), int(rc2.group(1)), stderr1
def test_failed_setup_step_releases_resources():
"""A step rejected by slurmstepd at setup does not strand the allocation.
srun reports the completion to the stepmgr, so the following step
runs. An over-range accounting frequency fails the setup under a memory
limit."""
rc1, rc2, stderr1 = run_failing_then_good_step(
"setup", "--acctg-freq=task=30", extra_sbatch_args="--mem=100"
)
assert rc1 != 0, "The step with an invalid accounting frequency should fail"
assert (
"Invalid accounting frequency requested" in stderr1
), f"The step should have been rejected at slurmstepd setup, got: {stderr1}"
assert rc2 == 0, "The following step should run once the failed step is released"
def test_prefork_failed_step_releases_resources():
"""A step rejected by slurmd before the fork does not strand the allocation.
srun reports the completion to the stepmgr, so the following step runs.
Binding to a CPU outside the step allocation fails the launch before the
slurmstepd is forked."""
# One past the largest node's CPUs is guaranteed outside any single-core
# step allocation, regardless of which core the scheduler picked.
outside_cpu = max(
int(node.get("cpus", 1)) for node in atf.get_nodes(quiet=True).values()
)
rc1, rc2, stderr1 = run_failing_then_good_step(
"prefork", f"--cpu-bind=map_cpu:{outside_cpu}"
)
assert rc1 != 0, "The step bound outside its allocation should fail"
assert (
"Unable to satisfy cpu bind request" in stderr1
), f"The step should have been rejected by slurmd before the fork, got: {stderr1}"
assert rc2 == 0, "The following step should run once the failed step is released"