blob: 8a3471618d1b4a5ae7d97e35e5b33b79ab04fe5f [file]
############################################################################
# Copyright (C) SchedMD LLC.
############################################################################
import os
import pytest
import atf
test_name = os.path.splitext(os.path.basename(__file__))[0]
qos_list = [f"{test_name}_qos{i}" for i in range(3)]
acct = f"{test_name}_acct"
resv_name = f"{test_name}_resv"
testuser = atf.properties["test-user"]
@pytest.fixture(scope="module", autouse=True)
def setup():
atf.require_version(
(25, 11),
"bin/scontrol",
reason="Creating reservations with qos= added in scontrol 25.11",
)
atf.require_accounting(True)
atf.require_config_parameter_includes("AccountingStorageEnforce", "qos")
atf.require_slurm_running()
@pytest.fixture(scope="module", autouse=True)
def setup_db():
# Create test QOS'es and a User that can use them
for qos in qos_list:
atf.run_command(
f"sacctmgr -i add qos {qos}",
user=atf.properties["slurm-user"],
fatal=True,
)
atf.run_command(
f"sacctmgr -i add account {acct}",
user=atf.properties["slurm-user"],
fatal=True,
)
qos_str = ",".join(qos_list)
atf.run_command(
f"sacctmgr -i add user {testuser} account={acct} qos={qos_str}",
user=atf.properties["slurm-user"],
fatal=True,
)
yield
atf.run_command(
f"sacctmgr -i remove user {testuser} account={acct}",
user=atf.properties["slurm-user"],
quiet=True,
)
atf.run_command(
f"sacctmgr -i remove account {acct}",
user=atf.properties["slurm-user"],
quiet=True,
)
for qos in qos_list:
atf.run_command(
f"sacctmgr -i remove qos {qos}",
user=atf.properties["slurm-user"],
quiet=True,
)
@pytest.fixture(scope="function")
def resv(request):
qos_access = f"qos={qos_list[0]}"
if hasattr(request, "param"):
qos_access = request.param
# Create the reservation for QOS test
result = atf.run_command(
f"scontrol create reservationname={resv_name} {qos_access} start=now duration=1 nodecnt=1",
user=atf.properties["slurm-user"],
)
assert (
result["exit_code"] == 0
), f"Couldn't create the reservation with {qos_access}"
yield resv_name
atf.run_command(
f"scontrol delete reservation {resv_name}",
user=atf.properties["slurm-user"],
quiet=True,
)
def test_invalid_args(resv):
"""Test that invalid configurations are rejected"""
# Mix allow and deny syntax
qos_access = f"qos={qos_list[0]},-{qos_list[1]}"
result = atf.run_command(
f"scontrol create reservationname=invalid_resv {qos_access} start=now duration=1 nodecnt=1",
user=atf.properties["slurm-user"],
)
assert (
result["exit_code"] != 0
), "Creating reservation with allowed and denied qos should fail"
# Default reservation only allows qos 0
# Clear all qos access control
result = atf.run_command(
f"scontrol update reservation ReservationName={resv} qos-={qos_list[0]}",
user=atf.properties["slurm-user"],
)
assert result["exit_code"] != 0, "Updating reservation to clear all qos should fail"
# Change from allow list to deny list
result = atf.run_command(
f"scontrol update reservation ReservationName={resv} qos=-{qos_list[0]}",
user=atf.properties["slurm-user"],
)
assert (
result["exit_code"] != 0
), "Updating reservation from allow list to deny list should fail"
tests = [
{
"qos_access": f"qos={qos_list[0]}",
"expected": [0],
"name": "=0",
},
{
"qos_access": f"qos={qos_list[0]},{qos_list[1]}",
"expected": [0, 1],
"name": "=0,1",
},
{
"qos_access": f"qos=-{qos_list[0]}",
"expected": [1, 2],
"name": "=-0",
},
{
"qos_access": f"qos=-{qos_list[0]},-{qos_list[1]}",
"expected": [2],
"name": "=-0,-1",
},
{
"qos_access": f"qos-={qos_list[0]},{qos_list[1]}",
"expected": [2],
"name": "-=0,1",
},
]
args = [(x["qos_access"], x["expected"]) for x in tests]
ids = [x["name"] for x in tests]
def assert_resv_access(resv, qos, is_allowed):
result = atf.run_command(
f"srun -N1 --reservation={resv} --account={acct} --qos={qos} true",
)
if is_allowed:
assert (
result["exit_code"] == 0
), "ExitCode wasn't 0. The job should not have been denied!"
assert (
"Problem using reservation" not in result["stderr"]
), "The job should not have been denied for user!"
else:
assert (
result["exit_code"] != 0
), "ExitCode was 0. The job should have been denied!"
def assert_resv_access_each(resv, expect):
# Try to run a job in each QOS
for i in range(len(qos_list)):
qos = qos_list[i]
assert_resv_access(resv, qos, i in expect)
@pytest.mark.parametrize("resv, expect", args, indirect=["resv"], ids=ids)
def test_reservation_qos(resv, expect):
"""Test that a reservation created with QOS= restricts access correctly"""
assert_resv_access_each(resv, expect)
def test_reservation_qos_any(resv):
"""Test that a job may use reservations when any QOS is allowed"""
# Default reservation only allows qos 0
assert_resv_access_each(resv, [0])
assert_resv_access(resv, f"{qos_list[0]},{qos_list[1]}", True)
assert_resv_access(resv, f"{qos_list[0]},{qos_list[2]}", True)
assert_resv_access(resv, f"{qos_list[1]},{qos_list[2]}", False)
def test_reservation_qos_update(resv):
"""Test that updating a reservation's QOS= restricts access correctly"""
# Add qos 1
atf.run_command(
f"scontrol update ReservationName={resv} qos+={qos_list[1]}",
user=atf.properties["slurm-user"],
fatal=True,
)
assert_resv_access_each(resv, [0, 1])
# Remove qos 0
atf.run_command(
f"scontrol update ReservationName={resv} qos-={qos_list[0]}",
user=atf.properties["slurm-user"],
fatal=True,
)
assert_resv_access_each(resv, [1])
# Set to qos 2
atf.run_command(
f"scontrol update ReservationName={resv} qos={qos_list[2]}",
user=atf.properties["slurm-user"],
fatal=True,
)
assert_resv_access_each(resv, [2])