blob: f0a734c0274ff0f66cc91f63fd63cf34b349671a [file] [edit]
############################################################################
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
############################################################################
import atf
import pytest
import re
cluster = None
# Setup
@pytest.fixture(scope="module", autouse=True)
def setup():
global cluster
atf.require_version((26, 5), component="bin/sacctmgr")
atf.require_nodes(1)
atf.require_config_parameter("AccountingStorageType", "accounting_storage/slurmdbd")
atf.require_config_parameter("LicenseParameters", "RemoteFuzzyMatch")
atf.require_config_parameter("Licenses", "simple:100")
atf.require_slurm_running()
cluster = atf.get_config_parameter("ClusterName", quiet=True)
@pytest.fixture(scope="function", autouse=True)
def delete_resources():
yield
output = atf.run_command_output(
"sacctmgr show resource -P -n Format=Name",
user=atf.properties["slurm-user"],
fatal=True,
).strip()
resources = []
for line in output.split("\n"):
if not line:
continue
resources.append(line)
if resources:
atf.run_command(
f"sacctmgr -i delete resource {' '.join(resources)}",
user=atf.properties["slurm-user"],
fatal=True,
)
@pytest.fixture(scope="function")
def matlab_res(setup):
res = "matlab"
atf.run_command(
f"sacctmgr -i create resource {res} server=slurmdb count=100 cluster={cluster} allowed=100",
user=atf.properties["slurm-user"],
fatal=True,
)
yield res
atf.run_command(
f"sacctmgr -i delete resource {res}",
user=atf.properties["slurm-user"],
fatal=True,
)
@pytest.mark.parametrize(
"resources,licenses",
[
(
# fuzzy match rewrites to include server and implicit count
"matlab",
"matlab@slurmdb:1",
),
(
# explicit match: not rewritten, no implicit quantity
"matlab@slurmdb",
"matlab@slurmdb",
),
(
# fuzzy match with explicit quantity
"matlab:5",
"matlab@slurmdb:5",
),
],
)
def test_fuzzy_license_match(matlab_res, resources, licenses):
"""
Licenses of a submitted job with --resources should be the expect ones
"""
job_id = atf.submit_job_sbatch(f'--resources={resources} --wrap="hostname"')
assert job_id != 0, "Job should be accepted with a valid license request"
assert atf.get_job_parameter(job_id, "Licenses") == licenses
def test_local_license_preferred():
"""
User should be able to submit a job with request "simple" and have it match
slurm.conf defined "simple" instead of remote license "simple@slurmdb".
"""
# create a remote license target resource
atf.run_command(
f"sacctmgr -i create resource simple server=slurmdb count=100 cluster={cluster} allowed=100",
user=atf.properties["slurm-user"],
fatal=True,
)
# submit job hoping to get local license
job_id = atf.submit_job_sbatch('--resources=simple --wrap="hostname"')
assert job_id != 0, "Job should be accepted with a valid var"
## NOTE fuzzy matcher function should be used which rewrites license
## string to simple:1 even though it is picking the local license
assert atf.get_job_parameter(job_id, "Licenses") == "simple:1"
def test_match_explicit_remote_with_local_present():
"""
User should be able to explicitly request a remote license "simple@slurmdb"
even when a local license "simple" is also defined in slurm.conf. The explicit
@server form is not rewritten and has no implicit quantity added.
"""
# create a remote license target resource
atf.run_command(
f"sacctmgr -i create resource simple server=slurmdb count=100 cluster={cluster} allowed=100",
user=atf.properties["slurm-user"],
fatal=True,
)
# submit job explicitly requesting the remote license
job_id = atf.submit_job_sbatch('--resources=simple@slurmdb --wrap="hostname"')
assert job_id != 0, "Job should be accepted with a valid license request"
## NOTE explicit match: not rewritten, no implicit quantity added
assert atf.get_job_parameter(job_id, "Licenses") == "simple@slurmdb"
def test_fuzzy_match_does_not_work_for_multiple_servers():
"""
User should not be able to perform a fuzzy match if there are multiple
entries that could match
"""
# create a remote license target resource
atf.run_command(
f"sacctmgr -i create resource matlab server=remote1 count=100 cluster={cluster} allowed=100",
user=atf.properties["slurm-user"],
fatal=True,
)
atf.run_command(
f"sacctmgr -i create resource matlab server=remote2 count=100 cluster={cluster} allowed=100",
user=atf.properties["slurm-user"],
fatal=True,
)
# submit job hoping to get local license
result = atf.run_job("--resources=matlab hostname", xfail=True)
assert result["exit_code"] != 0
assert (
"Unable to allocate resources: Invalid license specification"
in result["stderr"]
)
def test_fuzzy_match_does_not_work_for_partial_name(matlab_res):
"""
User should not be able to perform a fuzzy match if only a substring of the
prefix is supplied (e.g., "matla" should not match "matlab")
"""
# submit job hoping to get local license
result = atf.run_job("--resources=matla hostname", xfail=True)
assert result["exit_code"] != 0
assert (
"Unable to allocate resources: Invalid license specification"
in result["stderr"]
)
def test_show_license_fuzzy_match(matlab_res):
"""
User should be able to selectively show license information using fuzzy match
"""
# display the license
ret = atf.run_command(
"scontrol show licenses=matlab",
user=atf.properties["slurm-user"],
)
assert ret["exit_code"] == 0, "We should be able to selectively show license info"
license_name = re.search(r"LicenseName=(\S*)", ret["stdout"]).group(1)
assert license_name == "matlab@slurmdb"
def test_show_license_one_result_only():
"""
To facilitate scriptable behaviors, ensure only one result is produced
by `scontrol show license=<name>`.
"""
# create a remote license target resource
atf.run_command(
f"sacctmgr -i create resource matlab server=slurmdb count=100 cluster={cluster} allowed=100",
user=atf.properties["slurm-user"],
fatal=True,
)
# create a remote license target resource
atf.run_command(
f"sacctmgr -i create resource matlab server=slurmdb2 count=100 cluster={cluster} allowed=100",
user=atf.properties["slurm-user"],
fatal=True,
)
# display the license
ret = atf.run_command(
"scontrol show licenses=matlab",
user=atf.properties["slurm-user"],
xfail=True,
)
assert ret["exit_code"] == 1
assert (
ret["stderr"].strip()
== 'scontrol: error: query "matlab" matched more than one result, exiting.'
)
assert ret["stdout"] == ""
def test_show_license_invalid_query(matlab_res):
"""
To facilitate scriptable behaviors, ensure only exit code non-zero if no
matching licenses are found
"""
# display the license
ret = atf.run_command(
"scontrol show licenses=invalid",
user=atf.properties["slurm-user"],
xfail=True,
)
assert ret["exit_code"] == 1
assert (
ret["stderr"].strip()
== 'scontrol: error: query "invalid" matched zero licenses.'
)
assert ret["stdout"] == ""
def test_resv_license_fuzzy_match(matlab_res):
"""
User should be able to create a reservation using a fuzzy-matched name
"""
# create a reservation
ret = atf.run_command(
f"scontrol create reservation=resv user={atf.properties['slurm-user']} licenses=matlab:100 start=now duration=unlimited flags=ANY_NODES",
user=atf.properties["slurm-user"],
)
assert ret["exit_code"] == 0
# verify the reservation has the license
assert atf.get_reservation_parameter("resv", "Licenses") == "matlab@slurmdb:100"
show = atf.run_command_output("scontrol show licenses=matlab@slurmdb", fatal=True)
resv_count = re.search(r"Reserved=(\S*)", show).group(1)
assert resv_count == "100"
# submit job using fuzzy match and reservation
job_id = atf.submit_job_sbatch(
'--resources=matlab:10 --reservation=resv --wrap="sleep infinity"',
user=atf.properties["slurm-user"],
)
assert job_id != 0, "Job should be accepted with a valid var"
atf.wait_for_job_state(job_id, "RUNNING", fatal=True)
show = atf.run_command_output("scontrol show licenses=matlab@slurmdb", fatal=True)
resv_count = re.search(r"Reserved=(\S*)", show).group(1)
used_count = re.search(r"Used=(\S*)", show).group(1)
assert resv_count == "100"
assert used_count == "10"