blob: 893fbae3e9080a9aaf20356583084e407d61d5ea [file] [edit]
############################################################################
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
############################################################################
"""Verify the HTTP status slurmrestd returns for a request slurmdbd rejects.
A slurmdbd error reaches slurmrestd differently than a slurmctld one. It is
still a client error, so it must come back as 422 rather than 500, carrying
the error that names why.
"""
import os
import pytest
import requests
import atf
# Any data_parser works: the status code is chosen before the response is
# serialized. Pinned to the newest so it survives version removals longest.
data_parser = "v0.0.46"
test_name = os.path.splitext(os.path.basename(__file__))[0]
account_name = f"{test_name}_acct"
other_account_name = f"{test_name}_acct2"
user_name = atf.get_user_name()
@pytest.fixture(scope="module", autouse=True)
def setup():
atf.require_version(
(26, 11),
"sbin/slurmrestd",
reason="Ticket 25423: data_parser v0.0.46 was added in 26.11",
)
atf.require_slurmrestd("slurmctld,slurmdbd,util", data_parser)
atf.require_accounting(modify=True)
atf.require_config_parameter_includes("AuthAltTypes", "auth/jwt")
atf.require_config_parameter_includes("AuthAltTypes", "auth/jwt", source="slurmdbd")
# slurmdbd only refuses to remove a default account when a user is required
# to have one.
atf.require_config_parameter("AllowNoDefAcct", "No", source="slurmdbd")
atf.require_slurm_running()
@pytest.fixture(scope="module")
def admin_level(default_account):
"""Grants the test user the accounting rights an account removal needs."""
slurm_user = atf.properties["slurm-user"]
current = atf.run_command_output(
f"sacctmgr -n show user {user_name} format=AdminLevel",
user=slurm_user,
fatal=True,
).strip()
if current.startswith("Admin"):
yield
return
atf.run_command(
f"sacctmgr -i modify user {user_name} set adminlevel=Admin",
user=slurm_user,
fatal=True,
)
yield
atf.run_command(
f"sacctmgr -i modify user {user_name} set adminlevel={current or 'None'}",
user=slurm_user,
fatal=True,
)
@pytest.fixture(scope="module")
def default_account(setup):
"""Makes an account the test user's default, which blocks its removal.
slurmdbd only protects a default account when the user keeps another one.
"""
slurm_user = atf.properties["slurm-user"]
# An absent user prints nothing, a present one prints its name and its
# default account. Which of the two it is decides how much teardown has
# to undo, since a user added here has to go away with the accounts.
previous = atf.run_command_output(
f"sacctmgr -n show user {user_name} format=User,DefaultAccount",
user=slurm_user,
fatal=True,
).split()
user_existed = bool(previous)
previous_default = previous[1] if len(previous) > 1 else ""
atf.run_command(
f"sacctmgr -i add account {account_name},{other_account_name}",
user=slurm_user,
fatal=True,
)
atf.run_command(
f"sacctmgr -i add user {user_name} "
f"account={account_name},{other_account_name}",
user=slurm_user,
fatal=True,
)
atf.run_command(
f"sacctmgr -i modify user {user_name} set defaultaccount={account_name}",
user=slurm_user,
fatal=True,
)
yield account_name
# The accounts cannot be removed while one of them is still the user's
# default, so the user has to stop pointing at them first. A user that was
# already in the database gets its own default back; one added here has no
# default to go back to, so it goes away instead, which is how it was
# found. AllowNoDefAcct=No leaves no third case.
if previous_default:
atf.run_command(
f"sacctmgr -i modify user {user_name} "
f"set defaultaccount={previous_default}",
user=slurm_user,
fatal=True,
)
elif not user_existed:
atf.run_command(
f"sacctmgr -i remove user {user_name}",
user=slurm_user,
fatal=True,
)
atf.run_command(
f"sacctmgr -i remove account {account_name},{other_account_name}",
user=slurm_user,
fatal=True,
)
def test_default_account_removal_returns_422(default_account, admin_level):
"""Verify an account the slurmdbd refuses to remove answers 422."""
r = requests.delete(
f"{atf.properties['slurmrestd_url']}/slurmdb/{data_parser}/account/{default_account}",
headers=atf.properties["slurmrestd-headers"],
timeout=30,
)
assert r.status_code == 422, f"removal answered {r.status_code}: {r.text}"
body = r.json()
assert len(body["errors"]) == 1, f"removal should report one error: {r.text}"
assert body["errors"][0]["error"], f"removal should describe its error: {r.text}"
assert not body["warnings"], f"removal should warn about nothing: {r.text}"