blob: 159fddd91efbb0ca94314847780856dc43495fd9 [file] [edit]
############################################################################
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
############################################################################
"""Test that DefSegmentSize changes reach pending jobs."""
import pytest
import atf
# Setup
@pytest.fixture(scope="module", autouse=True)
def setup():
atf.require_version(
(26, 11), "sbin/slurmctld", reason="Issue 50961: DefSegmentSize added in 26.11"
)
atf.require_nodes(8)
atf.require_config_parameter("SelectType", "select/cons_tres")
atf.require_config_parameter("SelectTypeParameters", "CR_CPU")
atf.require_config_parameter("TopologyPlugin", "topology/block")
topology_config = """
BlockName=b1 Nodes=node[1-4]
BlockName=b2 Nodes=node[5-8]
BlockSizes=4,8
"""
atf.require_config_file("topology.conf", topology_config)
atf.require_config_parameter_includes("SchedulerParameters", ("bf_interval", 1))
atf.require_config_parameter_includes("SchedulerParameters", ("sched_interval", 1))
atf.require_slurm_running()
@pytest.fixture(scope="function")
def pending_jobs():
"""Fill the cluster, then leave two jobs pending behind it.
The first does not request a segment size, the second does.
"""
partition = atf.default_partition()
prior_defaults = atf.get_partition_parameter(partition, "JobDefaults")
set_job_defaults(partition, "DefSegmentSize=2")
blocker = submit_job("-N 8")
atf.wait_for_job_state(blocker, "RUNNING", fatal=True)
default_job = submit_job("-N 4")
requested_job = submit_job("-N 4 --segment=2")
wait_for_segment_size(default_job, 2)
wait_for_segment_size(requested_job, 2)
yield default_job, requested_job
set_job_defaults(partition, prior_defaults or "")
def submit_job(sbatch_args):
return atf.submit_job_sbatch(
f'{sbatch_args} --exclusive --mem=1 --wrap="sleep infinity"', fatal=True
)
def set_job_defaults(partition, specs):
atf.run_command(
f"scontrol update PartitionName={partition} JobDefaults={specs}",
user=atf.properties["slurm-user"],
fatal=True,
)
def set_job_segment_size(job_id, segment_size):
atf.run_command(
f"scontrol update job={job_id} SegmentSize={segment_size}",
user=atf.properties["slurm-user"],
fatal=True,
)
def segment_size(job_id):
return atf.get_job_parameter(
job_id, "SegmentSize", default=0, quiet=True, fatal=True
)
def wait_for_segment_size(job_id, expected):
"""Poll until the job's SegmentSize settles to the expected value.
The default is applied by the scheduler on each evaluation rather than at
submission, so after a DefSegmentSize change the new value only appears on
the job's next scheduling pass.
"""
for _ in atf.timer(fatal=True):
if segment_size(job_id) == expected:
return
def test_pending_job_follows_default_change(pending_jobs):
"""Verify a change of DefSegmentSize reaches pending jobs"""
default_job, requested_job = pending_jobs
set_job_defaults(atf.default_partition(), "DefSegmentSize=4")
# A pending job which did not request a segment size tracks the new
# default on its next evaluation; one which did is left alone.
wait_for_segment_size(default_job, 4)
assert (
segment_size(requested_job) == 2
), "A job which requested a segment size should not be affected"
new_job = submit_job("-N 4")
wait_for_segment_size(new_job, 4)
def test_pending_job_follows_default_clear(pending_jobs):
"""Verify clearing DefSegmentSize also reaches pending jobs"""
default_job, requested_job = pending_jobs
set_job_defaults(atf.default_partition(), "")
wait_for_segment_size(default_job, 0)
assert (
segment_size(requested_job) == 2
), "A job which requested a segment size should not be affected"
new_job = submit_job("-N 4")
wait_for_segment_size(new_job, 0)
def test_pending_job_can_opt_out(pending_jobs):
"""Verify a pending job can opt out of DefSegmentSize"""
atf.require_version(
(26, 11),
"bin/scontrol",
reason="Issue 50852: scontrol update job SegmentSize added in 26.11",
)
default_job, requested_job = pending_jobs
# An explicit SegmentSize=0 pins the job as user-set, so the
# partition default no longer applies to it: the job runs unsegmented.
set_job_segment_size(default_job, 0)
wait_for_segment_size(default_job, 0)
# The default is only applied when the scheduler evaluates a job, so a
# new job picking up the changed default shows that a scheduling pass
# has run. The opted-out job must not have picked it up by then.
set_job_defaults(atf.default_partition(), "DefSegmentSize=4")
new_job = submit_job("-N 4")
wait_for_segment_size(new_job, 4)
assert (
segment_size(default_job) == 0
), "Opted-out job should not get the changed default"
assert (
segment_size(requested_job) == 2
), "A job which requested a segment size should not be affected"
def test_invalid_value_rejected():
"""Verify a segment size which does not fit a uint16 is rejected"""
output = atf.run_command_error(
f"scontrol update PartitionName={atf.default_partition()} "
"JobDefaults=DefSegmentSize=70000",
user=atf.properties["slurm-user"],
xfail=True,
)
assert "Invalid JobDefaults" in output, "Out of range value should be rejected"