blob: 7e927962178fed1c980c8e5882f4e6c9ee2da900 [file]
############################################################################
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
############################################################################
"""Tests for the partition MinNodes limit with task-count jobs."""
import os
import pytest
import atf
test_name = os.path.splitext(os.path.basename(__file__))[0]
PARTITION_NAME = "min_nodes_part"
MIN_NODES = 2
LOW_MIN_NODES = 1
@pytest.fixture(scope="module", autouse=True)
def setup():
atf.require_config_parameter("EnforcePartLimits", ["NO", None])
atf.require_nodes(2, [("CPUs", 4), ("RealMemory", 1024)])
atf.require_slurm_running()
@pytest.fixture(scope="function")
def partition():
created = []
def _create(name, min_nodes):
nodes = list(atf.get_nodes().keys())
node_range = atf.node_list_to_range(nodes[0:2])
atf.run_command(
f"scontrol create PartitionName={name} "
f"Nodes={node_range} MinNodes={min_nodes}",
fatal=True,
user=atf.properties["slurm-user"],
)
created.append(name)
yield _create
atf.cancel_jobs(atf.properties["submitted-jobs"], quiet=True)
for name in created:
atf.run_command(
f"scontrol delete PartitionName={name}",
fatal=True,
user=atf.properties["slurm-user"],
)
@pytest.fixture(scope="function")
def min_nodes_partition(partition):
partition(PARTITION_NAME, MIN_NODES)
@pytest.fixture(scope="function")
def unlimited_partition(partition):
partition(PARTITION_NAME, LOW_MIN_NODES)
def test_job_meets_min_nodes(min_nodes_partition):
"""Verify the implicit max_nodes is not derived below the partition MinNodes"""
job_id = atf.submit_job_sbatch(
f"-p {PARTITION_NAME} -n2 --ntasks-per-node=2 -J {test_name}"
' -t 1 --wrap "hostname" -o /dev/null',
fatal=True,
)
completed = atf.wait_for_job_state(job_id, "COMPLETED")
state = atf.get_job_parameter(job_id, "JobState", quiet=True)
reason = atf.get_job_parameter(job_id, "Reason", quiet=True)
assert (
completed
), f"Job should have run on {MIN_NODES} nodes: state={state} reason={reason}"
node_list = atf.node_range_to_list(atf.get_job_parameter(job_id, "NodeList"))
assert (
len(node_list) == MIN_NODES
), f"Job should allocate {MIN_NODES} node(s), got {len(node_list)}: {node_list}"
def test_raised_min_nodes_pends_until_job_updated(unlimited_partition):
"""Verify a raised MinNodes pends a no-node-count job until NumNodes is set"""
job_id = atf.submit_job_sbatch(
f"--hold -p {PARTITION_NAME} -n2 --ntasks-per-node=2 -J {test_name}"
' -t 1 --wrap "hostname" -o /dev/null',
fatal=True,
)
atf.run_command(
f"scontrol update PartitionName={PARTITION_NAME} MinNodes={MIN_NODES}",
fatal=True,
user=atf.properties["slurm-user"],
)
atf.run_command(f"scontrol release {job_id}", fatal=True)
atf.wait_for_job_state(
job_id, "PENDING", desired_reason="PartitionNodeLimit", fatal=True
)
atf.run_command(
f"scontrol update jobid={job_id} NumNodes={MIN_NODES}",
fatal=True,
user=atf.properties["slurm-user"],
)
atf.wait_for_job_state(job_id, "COMPLETED", fatal=True)
node_list = atf.node_range_to_list(atf.get_job_parameter(job_id, "NodeList"))
assert (
len(node_list) == MIN_NODES
), f"Job should allocate {MIN_NODES} node(s), got {len(node_list)}: {node_list}"