| ############################################################################ | |
| # Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| ############################################################################ | |
| import pytest | |
| import atf | |
| @pytest.fixture(scope="module", autouse=True) | |
| def setup(): | |
| atf.require_config_parameter( | |
| "PartitionName", | |
| { | |
| "p1": {"Nodes": "ALL", "OverSubscribe": "YES", "Default": "YES"}, | |
| "p2": {"Nodes": "ALL", "OverSubscribe": "YES"}, | |
| }, | |
| ) | |
| # Allow more than 4 jobs in terms of memory | |
| atf.require_nodes(1, [("CPUs", 1), ("RealMemory", 2048)]) | |
| atf.require_config_parameter("DefMemPerNode", 256) | |
| atf.require_slurm_running() | |
| def test_oversubscribe_only(): | |
| """ | |
| Verify behavior of --oversubscribe option. | |
| Submits a job with --oversubscribe and --hold, then checks job parameters. | |
| """ | |
| job_id = atf.submit_job_sbatch( | |
| "--oversubscribe --hold -t1 --wrap='sleep infinity'", fatal=True | |
| ) | |
| # Issue 20588 (26.05): scontrol may show OverSubscribe=OK instead of YES. | |
| expected = {"YES", "OK"} if atf.get_version() >= (26, 5) else {"YES"} | |
| assert ( | |
| atf.get_job_parameter(job_id, "OverSubscribe") in expected | |
| ), f"OverSubscribe flag should be one of {expected}" | |
| assert ( | |
| atf.get_job_parameter(job_id, "Contiguous") == 0 | |
| ), "Contiguous flag should be 0" | |
| def test_contiguous_only(): | |
| """ | |
| Verify behavior of --contiguous option. | |
| Submits a job with --contiguous and --hold, then checks job parameters. | |
| """ | |
| job_id = atf.submit_job_sbatch( | |
| "--contiguous --hold -t1 --wrap='sleep infinity'", fatal=True | |
| ) | |
| # A non-shareable job shows OverSubscribe=NO, or OK on 26.05+ (Issue 20588). | |
| assert atf.get_job_parameter(job_id, "OverSubscribe") in { | |
| "NO", | |
| "OK", | |
| }, "OverSubscribe flag should be 'NO' or 'OK'" | |
| assert ( | |
| atf.get_job_parameter(job_id, "Contiguous") == 1 | |
| ), "Contiguous flag should be 1" | |
| def test_overlapping_oversubscribe(): | |
| """ | |
| Jobs without -s on overlapping OverSubscribe=YES partitions shouldn't run concurrently. | |
| """ | |
| # Submit first job to p1 without --oversubscribe | |
| job_id1 = atf.submit_job_sbatch("-p p1 --wrap='sleep infinity'", fatal=True) | |
| # Wait for job 1 to start running | |
| atf.wait_for_job_state(job_id1, "RUNNING", fatal=True) | |
| # Submit second job to p2 without --oversubscribe | |
| job_id2 = atf.submit_job_sbatch("-p p2 --wrap='sleep infinity'", fatal=True) | |
| # Verify job 2 is PENDING with Reason=Resources | |
| assert atf.wait_for_job_state( | |
| job_id2, | |
| "PENDING", | |
| "Resources", | |
| ), "Job2 should be PENDING due Resources" | |
| # Verify job 1 is still running | |
| assert ( | |
| atf.get_job_parameter(job_id1, "JobState") == "RUNNING" | |
| ), "Job 1 should still be RUNNING" | |
| def test_overlapping_oversubscribe_sharing_restriction_1(): | |
| """ | |
| Job with -s should be blocked if a non -s job is running on overlapping partition. | |
| """ | |
| # Submit first job to p2 without --oversubscribe | |
| job_id1 = atf.submit_job_sbatch("-p p2 --wrap='sleep infinity'", fatal=True) | |
| # Wait for job 1 to start running | |
| atf.wait_for_job_state(job_id1, "RUNNING", fatal=True) | |
| # Submit second job to p1 *with* --oversubscribe | |
| job_id2 = atf.submit_job_sbatch( | |
| "-p p1 --oversubscribe --wrap='sleep infinity'", fatal=True | |
| ) | |
| # Verify job 2 is PENDING with Reason=Resources | |
| assert atf.wait_for_job_state( | |
| job_id2, | |
| "PENDING", | |
| "Resources", | |
| ), "Job2 should be PENDING due Resources" | |
| # Verify job 1 is still running | |
| assert ( | |
| atf.get_job_parameter(job_id1, "JobState") == "RUNNING" | |
| ), "Job 1 should still be RUNNING" | |
| def test_overlapping_oversubscribe_sharing_restriction_2(): | |
| """ | |
| Job without -s should be blocked if a job with -s is running on overlapping partition. | |
| """ | |
| # Submit first job to p2 *with* --oversubscribe | |
| job_id1 = atf.submit_job_sbatch( | |
| "-p p2 --oversubscribe --wrap='sleep infinity'", fatal=True | |
| ) | |
| # Wait for job 1 to start running | |
| atf.wait_for_job_state(job_id1, "RUNNING", fatal=True) | |
| # Submit second job to p1 *without* --oversubscribe | |
| job_id2 = atf.submit_job_sbatch("-p p1 --wrap='sleep infinity'", fatal=True) | |
| # Verify job 2 is PENDING with Reason=Resources | |
| assert atf.wait_for_job_state( | |
| job_id2, | |
| "PENDING", | |
| "Resources", | |
| ), "Job2 should be PENDING due Resources" | |
| # Verify job 1 is still running | |
| assert ( | |
| atf.get_job_parameter(job_id1, "JobState") == "RUNNING" | |
| ), "Job 1 should still be RUNNING" | |
| def test_oversubscribe_same_partition(): | |
| """ | |
| Test that two jobs *with* --oversubscribe can run on the same node | |
| if they are in the same partition (p1). | |
| """ | |
| # Submit first job to p1 with --oversubscribe | |
| job_id1 = atf.submit_job_sbatch( | |
| "-p p1 --oversubscribe --wrap='sleep infinity'", fatal=True | |
| ) | |
| # Wait for job 1 to start running | |
| atf.wait_for_job_state(job_id1, "RUNNING", fatal=True) | |
| node1 = atf.get_job_parameter(job_id1, "NodeList") | |
| # Submit second job to p1 *with* --oversubscribe | |
| job_id2 = atf.submit_job_sbatch( | |
| "-p p1 --oversubscribe --wrap='sleep infinity'", fatal=True | |
| ) | |
| # Wait for job 2 to start running (should run concurrently) | |
| atf.wait_for_job_state(job_id2, "RUNNING", fatal=True) | |
| node2 = atf.get_job_parameter(job_id2, "NodeList") | |
| # Sanity check | |
| if node1 is None or node2 is None: | |
| pytest.fail(f"Both nodes should be specified but got {node1} and {node2}") | |
| # Verify both jobs are running on the same node | |
| assert node1 == node2, f"Jobs should run on the same node ({node1} vs {node2})" |