blob: fb790c1b67da60c9bda514bcc5bae389ba09e668 [file]
############################################################################
# "Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved
############################################################################
import re
import pytest
import atf
# Setup
@pytest.fixture(scope="module", autouse=True)
def setup():
atf.require_nodes(8)
atf.require_config_parameter("SelectType", "select/cons_tres")
atf.require_config_parameter("SelectTypeParameters", "CR_Core_Memory")
atf.require_config_parameter("TopologyPlugin", "topology/block")
atf.require_version((26, 5), "sbin/slurmctld")
topology_config = """
BlockName=b1 Nodes=node[1,3,5,7]
BlockName=b2 Nodes=node[2,4,6,8]
BlockSizes=4,8
"""
atf.require_config_file("topology.conf", topology_config)
atf.require_slurm_running()
# b1 before b2, so topology order differs from both bitmap and alphabetical
block_order = ["node1", "node3", "node5", "node7", "node2", "node4", "node6", "node8"]
def _parse_task_nodes(output):
"""Parse 'tid: nodename' lines, return list of (tid, node) sorted by tid."""
matches = re.findall(r"(\d+): (\S+)", output)
return sorted(matches, key=lambda x: int(x[0]))
def test_topo_task_order():
"""Verify tasks are distributed following topology block order,
not bitmap order."""
output = atf.run_job_output(
"-N8 -n8 --exclusive --mem=1 -m block -l printenv SLURMD_NODENAME",
fatal=True,
)
tasks = _parse_task_nodes(output)
actual = [node for _, node in tasks]
assert actual == block_order, (
f"Tasks should follow topology block order: "
f"expected {block_order}, got {actual}"
)
def test_topo_emitted_lists_match_task_order():
"""The emitted node lists follow the same block order as the tasks.
SLURM_NODEID is a position in SLURM_STEP_NODELIST, so a list left in
bitmap order would report task 0 on a node other than the one it ran on.
"""
for component in ["sbin/slurmctld", "sbin/slurmd"]:
atf.require_version(
(26, 11),
component,
reason=f"Issue 50993: {component} emits node lists in topology-rank "
f"order in 26.11+",
)
output = atf.run_job_output(
"-N8 -n8 --exclusive --mem=1 -m block -l bash -c "
"'echo $SLURM_NODEID $SLURMD_NODENAME $SLURM_STEP_NODELIST "
"$SLURM_JOB_NODELIST'",
fatal=True,
)
matches = re.findall(r"(\d+): (\d+) (\S+) (\S+) (\S+)", output)
matches.sort(key=lambda x: int(x[0]))
assert matches, f"Expected tasks to report their placement, got: {output}"
nodes = [m[2] for m in matches]
node_ids = [int(m[1]) for m in matches]
step_list, job_list = matches[0][3], matches[0][4]
assert nodes == block_order, f"Tasks should follow {block_order}, got {nodes}"
assert (
atf.node_range_to_list(step_list) == block_order
), f"SLURM_STEP_NODELIST should be {block_order}, got {step_list}"
assert (
atf.node_range_to_list(job_list) == block_order
), f"SLURM_JOB_NODELIST should be {block_order}, got {job_list}"
assert node_ids == list(range(len(block_order))), (
f"SLURM_NODEID should index SLURM_STEP_NODELIST: expected "
f"{list(range(len(block_order)))}, got {node_ids}"
)
def test_topo_multi_step_rotation():
"""Verify consecutive steps rotate across topology blocks."""
job_id = atf.submit_job_sbatch(
"-N8 --exclusive --mem=1 --wrap 'sleep infinity'", fatal=True
)
atf.wait_for_step(job_id, "batch")
step1_output = atf.run_job_output(
f"--jobid={job_id} -N3 -n3 -m block -l printenv SLURMD_NODENAME", fatal=True
)
step2_output = atf.run_job_output(
f"--jobid={job_id} -N3 -n3 -m block -l printenv SLURMD_NODENAME", fatal=True
)
step1_nodes = [node for _, node in _parse_task_nodes(step1_output)]
step2_nodes = [node for _, node in _parse_task_nodes(step2_output)]
expected_step1_nodes = ["node1", "node3", "node5"]
expected_step2_nodes = ["node2", "node4", "node6"]
assert (
step1_nodes == expected_step1_nodes
), f"Step 1 expected {expected_step1_nodes}, got {step1_nodes}"
assert (
step2_nodes == expected_step2_nodes
), f"Step 2 expected {expected_step2_nodes}, got {step2_nodes}"