| ############################################################################ | |
| # Copyright (C) SchedMD LLC. | |
| ############################################################################ | |
| import os | |
| import pytest | |
| import atf | |
| test_name = os.path.splitext(os.path.basename(__file__))[0] | |
| partitions = [f"{test_name}_part{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 AllowedPartition= added in scontrol 25.11", | |
| ) | |
| atf.require_accounting(True) | |
| atf.require_slurm_running() | |
| @pytest.fixture(scope="module", autouse=True) | |
| def setup_db(): | |
| # Create test Partitions | |
| for part in partitions: | |
| atf.run_command( | |
| f"scontrol create PartitionName={part} Nodes=ALL", | |
| user=atf.properties["slurm-user"], | |
| fatal=True, | |
| ) | |
| yield | |
| for part in partitions: | |
| atf.run_command( | |
| f"scontrol delete PartitionName={part}", | |
| user=atf.properties["slurm-user"], | |
| quiet=True, | |
| ) | |
| @pytest.fixture(scope="function") | |
| def resv(request): | |
| part_access = f"AllowedPartition={partitions[0]}" | |
| if hasattr(request, "param"): | |
| part_access = request.param | |
| result = atf.run_command( | |
| f"scontrol create ReservationName={resv_name} {part_access} Start=now Duration=1 NodeCnt=1", | |
| user=atf.properties["slurm-user"], | |
| ) | |
| assert result["exit_code"] == 0, "Couldn't create the reservation!" | |
| 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 | |
| part_access = f"AllowedPartitions={partitions[0]},-{partitions[1]}" | |
| result = atf.run_command( | |
| f"scontrol create ReservationName=invalid_resv {part_access} Start=now Duration=1 Nodecnt=1", | |
| user=atf.properties["slurm-user"], | |
| ) | |
| assert ( | |
| result["exit_code"] != 0 | |
| ), "Creating reservation with allowed and denied partitions should fail" | |
| # Default reservation only allows partition 0 | |
| # Clear all partition access control | |
| result = atf.run_command( | |
| f"scontrol update reservation ReservationName={resv} AllowedPartitions-={partitions[0]}", | |
| user=atf.properties["slurm-user"], | |
| ) | |
| assert ( | |
| result["exit_code"] != 0 | |
| ), "Updating reservation to clear all partitions should fail" | |
| # Change from allow list to deny list | |
| result = atf.run_command( | |
| f"scontrol update reservation ReservationName={resv} AllowedPartitions=-{partitions[0]}", | |
| user=atf.properties["slurm-user"], | |
| ) | |
| assert ( | |
| result["exit_code"] != 0 | |
| ), "Updating reservation from allow list to deny list should fail" | |
| tests = [ | |
| { | |
| "part_access": f"AllowedPartitions={partitions[0]}", | |
| "expected": [0], | |
| "name": "=0", | |
| }, | |
| { | |
| "part_access": f"AllowedPartitions={partitions[0]},{partitions[1]}", | |
| "expected": [0, 1], | |
| "name": "=0,1", | |
| }, | |
| { | |
| "part_access": f"AllowedPartitions=-{partitions[0]}", | |
| "expected": [1, 2], | |
| "name": "=-0", | |
| }, | |
| { | |
| "part_access": f"AllowedPartitions=-{partitions[0]},-{partitions[1]}", | |
| "expected": [2], | |
| "name": "=-0,-1", | |
| }, | |
| { | |
| "part_access": f"AllowedPartitions-={partitions[0]},{partitions[1]}", | |
| "expected": [2], | |
| "name": "-=0,1", | |
| }, | |
| ] | |
| args = [(x["part_access"], x["expected"]) for x in tests] | |
| ids = [x["name"] for x in tests] | |
| def assert_resv_access(resv, part, is_allowed): | |
| result = atf.run_command( | |
| f"srun -N1 --reservation={resv} --partition={part} 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 partition | |
| for i in range(len(partitions)): | |
| part = partitions[i] | |
| assert_resv_access(resv, part, i in expect) | |
| @pytest.mark.parametrize("resv, expect", args, indirect=["resv"], ids=ids) | |
| def test_reservation_partition(resv, expect): | |
| """Test that a reservation created with AllowedPartitions= restricts access correctly""" | |
| assert_resv_access_each(resv, expect) | |
| def test_reservation_partition_any(resv): | |
| """Test that a job may use reservations when any partition is allowed""" | |
| # Default reservation only allows part 0 | |
| assert_resv_access_each(resv, [0]) | |
| assert_resv_access(resv, f"{partitions[0]},{partitions[1]}", True) | |
| assert_resv_access(resv, f"{partitions[0]},{partitions[2]}", True) | |
| assert_resv_access(resv, f"{partitions[1]},{partitions[2]}", False) | |
| def test_reservation_partition_update(resv): | |
| """Test that updating a reservation's AllowedPartitions= restricts access correctly""" | |
| # Add part 1 | |
| atf.run_command( | |
| f"scontrol update ReservationName={resv} AllowedPartitions+={partitions[1]}", | |
| user=atf.properties["slurm-user"], | |
| fatal=True, | |
| ) | |
| assert_resv_access_each(resv, [0, 1]) | |
| # Remove part 0 | |
| atf.run_command( | |
| f"scontrol update ReservationName={resv} AllowedPartitions-={partitions[0]}", | |
| user=atf.properties["slurm-user"], | |
| fatal=True, | |
| ) | |
| assert_resv_access_each(resv, [1]) | |
| # Set to part 2 | |
| atf.run_command( | |
| f"scontrol update ReservationName={resv} AllowedPartitions={partitions[2]}", | |
| user=atf.properties["slurm-user"], | |
| fatal=True, | |
| ) | |
| assert_resv_access_each(resv, [2]) |