blob: 632a7d97c100a57cba7e6b4887e5a24d5a6d905c [file] [edit]
############################################################################
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
############################################################################
"""Test 102.13: sacctmgr show assoc withsubaccounts with partition assocs.
WithSubAccounts selects the associations under an account by matching the
account in their lineage. A partition-based association carries its
partition as the last lineage segment, so an association whose partition is
named like the requested account must not be selected (Ticket 23099).
"""
import re
import pytest
import atf
cluster1 = "test102_13_cluster"
parent_account = "test102_13_parent"
child_account = "test102_13_child"
partition_user = "test102_13_puser"
child_user = "test102_13_cuser"
@pytest.fixture(scope="module", autouse=True)
def setup():
# sacctmgr is probed rather than sreport because sreport -V exits before
# parsing options unless accounting_storage/slurmdbd is configured.
# Lower this gate to the first maintenance releases that carry the fix
# once the backports land.
atf.require_version(
(26, 11),
"bin/sacctmgr",
reason="Ticket 23099: WithSubAccounts lineage matching fixed in 26.11",
)
atf.require_accounting(modify=True)
atf.require_slurm_running()
@pytest.fixture(scope="module")
def create_entities():
"""Populate the accounting database with the cluster, accounts and users"""
# Clean up after an aborted earlier run; the removal is expected to fail
# when nothing was left behind. Removing a cluster cascades a delete
# across every per-cluster usage table, which can exceed the default
# command timeout on a loaded DB.
atf.run_command(
f"sacctmgr -i remove cluster {cluster1}",
user=atf.properties["slurm-user"],
timeout=120,
quiet=True,
)
atf.run_command(
f"sacctmgr -i add cluster {cluster1}",
user=atf.properties["slurm-user"],
fatal=True,
)
atf.run_command(
f"sacctmgr -i add account {parent_account} cluster={cluster1}",
user=atf.properties["slurm-user"],
fatal=True,
)
atf.run_command(
f"sacctmgr -i add account {child_account} cluster={cluster1} parent={parent_account}",
user=atf.properties["slurm-user"],
fatal=True,
)
# The partition is named like the child account, so this association's
# lineage ends with "/<child_account>/" without being under it
atf.run_command(
f"sacctmgr -i add user user={partition_user} cluster={cluster1} account={parent_account} partition={child_account}",
user=atf.properties["slurm-user"],
fatal=True,
)
atf.run_command(
f"sacctmgr -i add user user={child_user} cluster={cluster1} account={child_account}",
user=atf.properties["slurm-user"],
fatal=True,
)
yield
# The cascading delete can exceed the default command timeout
atf.run_command(
f"sacctmgr -i remove cluster {cluster1}",
user=atf.properties["slurm-user"],
timeout=120,
fatal=True,
)
# Users and accounts are not cluster-scoped, so removing the cluster
# leaves them behind
atf.run_command(
f"sacctmgr -i remove user {partition_user},{child_user}",
user=atf.properties["slurm-user"],
fatal=True,
)
atf.run_command(
f"sacctmgr -i remove account {parent_account},{child_account}",
user=atf.properties["slurm-user"],
fatal=True,
)
@pytest.fixture(scope="module")
def subaccount_output(create_entities):
"""Return the withsubaccounts listing for the child account"""
command = f"sacctmgr -n -P show assoc cluster={cluster1} account={child_account} withsubaccounts format=Account,User,Partition"
return atf.run_command_output(command, fatal=True)
@pytest.fixture(scope="module")
def parent_subaccount_output(create_entities):
"""Return the withsubaccounts listing for the parent account"""
command = f"sacctmgr -n -P show assoc cluster={cluster1} account={parent_account} withsubaccounts format=Account,User,Partition"
return atf.run_command_output(command, fatal=True)
def test_sub_account_is_listed(parent_subaccount_output):
"""Requesting an account must also list its sub-account"""
assert (
re.search(rf"^{child_account}\|\|$", parent_subaccount_output, re.MULTILINE)
is not None
), f"{child_account} should be listed under {parent_account}"
def test_sub_account_user_is_listed(parent_subaccount_output):
"""Requesting an account must also list a user in its sub-account"""
assert (
re.search(
rf"^{child_account}\|{child_user}\|$",
parent_subaccount_output,
re.MULTILINE,
)
is not None
), f"{child_user} should be listed under {parent_account}"
def test_own_association_is_listed(subaccount_output):
"""The requested account's own association must be listed"""
assert (
re.search(rf"^{child_account}\|\|$", subaccount_output, re.MULTILINE)
is not None
), f"{child_account} should list its own association"
def test_user_association_is_listed(subaccount_output):
"""A user association under the requested account must be listed"""
assert (
re.search(
rf"^{child_account}\|{child_user}\|$", subaccount_output, re.MULTILINE
)
is not None
), f"{child_user} should be listed under {child_account}"
def test_partition_named_like_account_is_not_listed(subaccount_output):
"""An association whose partition is named like the requested account
must not be listed"""
assert (
re.search(rf"^[^|]*\|{partition_user}\|", subaccount_output, re.MULTILINE)
is None
), f"{partition_user} has a partition named {child_account}, not an association under it"