| ############################################################################ | |
| # Copyright (C) SchedMD LLC. | |
| ############################################################################ | |
| import pytest | |
| import atf | |
| # A list of the nodes used in the test | |
| node_list = [] | |
| node_range = "" | |
| # Setup for all tests | |
| @pytest.fixture(scope="module", autouse=True) | |
| def setup(): | |
| global node_range | |
| atf.require_nodes(8) | |
| atf.require_slurm_running() | |
| # Get a list of 8 idle nodes to be used in the tests | |
| node_list.extend(atf.run_job_nodes("-N8 true", fatal=True)) | |
| node_range = atf.node_list_to_range(node_list) | |
| @pytest.fixture(scope="function", autouse=True) | |
| def reset_node_states(): | |
| """Wait for all nodes to be idle before each test; cancel jobs + resume after.""" | |
| atf.wait_for_node_state(node_list, "IDLE", operator="==", fatal=True) | |
| yield | |
| atf.cancel_all_jobs() | |
| atf.run_command( | |
| f"scontrol update nodename={node_range} state=resume", | |
| user=atf.properties["slurm-user"], | |
| quiet=True, | |
| ) | |
| def test_allocated_state(): | |
| """Test sinfo filtering for ALLOCATED state.""" | |
| # Submit a job to allocate the first two nodes | |
| job_id = atf.submit_job_sbatch( | |
| f"-N2 -w {node_list[0]},{node_list[1]} --exclusive --wrap 'sleep 60'" | |
| ) | |
| atf.wait_for_job_state(job_id, "RUNNING", fatal=True) | |
| # Get sinfo output for allocated nodes | |
| output = atf.run_command_output( | |
| f"sinfo -Nh -n {node_range} -O NodeList --state=allocated" | |
| ) | |
| # Verify that only the two allocated nodes are in the output | |
| assert len(output.splitlines()) == 2, "Expected 2 allocated nodes" | |
| assert node_list[0] in output, f"{node_list[0]} should be allocated" | |
| assert node_list[1] in output, f"{node_list[1]} should be allocated" | |
| def test_idle_state(): | |
| """Test sinfo filtering for IDLE state.""" | |
| # Change the state of some nodes | |
| atf.run_command( | |
| f"scontrol update nodename={node_list[4]},{node_list[5]} state=down reason=test", | |
| user=atf.properties["slurm-user"], | |
| ) | |
| atf.run_command( | |
| f"scontrol update nodename={node_list[6]},{node_list[7]} state=drain reason=test", | |
| user=atf.properties["slurm-user"], | |
| ) | |
| # Get sinfo output for idle nodes | |
| output = atf.run_command_output( | |
| f"sinfo -Nh -n {node_range} -O NodeList,StateComplete --state=idle" | |
| ) | |
| # Verify that the correct nodes are reported as idle (including idle+drain) | |
| lines = [line.strip().split() for line in output.strip().splitlines()] | |
| nodes = {node: status for node, status in lines} | |
| nodes_idle = [n for n, s in nodes.items() if s == "idle"] | |
| nodes_drain = [n for n, s in nodes.items() if "drain" in s] | |
| assert len(nodes) == 6, "Expected 6 idle nodes, 4 only idle and 2 idle+drain nodes" | |
| assert len(nodes_idle) == 4, "Expected 4 idle nodes" | |
| for i in range(4): | |
| assert node_list[i] in nodes_idle, f"{node_list[i]} should be only idle" | |
| # Drained nodes are also idle | |
| assert len(nodes_drain) == 2, "Expected 2 drained* nodes (that are also idle)" | |
| assert node_list[6] in nodes_drain, f"{node_list[6]} should be idle+drain" | |
| assert node_list[7] in nodes_drain, f"{node_list[7]} should be idle+drain" | |
| def test_down_state(): | |
| """Test sinfo filtering for DOWN state.""" | |
| # Change the state of some nodes | |
| atf.run_command( | |
| f"scontrol update nodename={node_list[4]},{node_list[5]} state=down reason=test", | |
| user=atf.properties["slurm-user"], | |
| ) | |
| # Get sinfo output for down nodes | |
| output = atf.run_command_output( | |
| f"sinfo -Nh -n {node_range} -O NodeList,StateComplete --state=down" | |
| ) | |
| # Verify that the correct nodes are reported as down | |
| lines = [line.strip().split() for line in output.strip().splitlines()] | |
| nodes = {node: status for node, status in lines} | |
| nodes_down = [n for n, s in nodes.items() if "down" in s] | |
| assert len(nodes) == 2, "Expected 2 down nodes" | |
| assert node_list[4] in nodes_down, f"{node_list[4]} should be down" | |
| assert node_list[5] in nodes_down, f"{node_list[5]} should be down" | |
| def test_drain_state(): | |
| """Test sinfo filtering for DRAIN state.""" | |
| # Change the state of some nodes | |
| atf.run_command( | |
| f"scontrol update nodename={node_list[6]},{node_list[7]} state=drain reason=test", | |
| user=atf.properties["slurm-user"], | |
| ) | |
| # Get sinfo output for drained nodes | |
| output = atf.run_command_output( | |
| f"sinfo -Nh -n {node_range} -O NodeList,StateComplete --state=drain" | |
| ) | |
| # Verify that the correct nodes are reported as drained | |
| lines = [line.strip().split() for line in output.strip().splitlines()] | |
| nodes = {node: status for node, status in lines} | |
| nodes_drain = [n for n, s in nodes.items() if "drain" in s] | |
| assert len(nodes_drain) == 2, "Expected 2 drained nodes" | |
| assert node_list[6] in nodes_drain, f"{node_list[6]} should be drained" | |
| assert node_list[7] in nodes_drain, f"{node_list[7]} should be drained" | |
| def test_down_and_drain_state(): | |
| """Test sinfo filtering for DOWN and DRAIN states with a comma.""" | |
| # Change the state of some nodes | |
| atf.run_command( | |
| f"scontrol update nodename={node_list[4]},{node_list[5]} state=down reason=test", | |
| user=atf.properties["slurm-user"], | |
| ) | |
| atf.run_command( | |
| f"scontrol update nodename={node_list[6]},{node_list[7]} state=drain reason=test", | |
| user=atf.properties["slurm-user"], | |
| ) | |
| # Get sinfo output for down and drained nodes | |
| output = atf.run_command_output( | |
| f"sinfo -Nh -n {node_range} -O NodeList,StateComplete --state=down,drain" | |
| ) | |
| lines = [line.strip().split() for line in output.strip().splitlines()] | |
| nodes = {node: status for node, status in lines} | |
| nodes_down = [n for n, s in nodes.items() if "down" in s] | |
| nodes_drain = [n for n, s in nodes.items() if "drain" in s] | |
| # Verify the output | |
| assert len(nodes_down) == 2, "Expected 2 down nodes" | |
| assert len(nodes_drain) == 2, "Expected 2 drained nodes" | |
| assert node_list[4] in nodes_down, f"{node_list[4]} should be down" | |
| assert node_list[5] in nodes_down, f"{node_list[5]} should be down" | |
| assert node_list[6] in nodes_drain, f"{node_list[6]} should be drained" | |
| assert node_list[7] in nodes_drain, f"{node_list[7]} should be drained" | |
| def test_down_and_drain_state_ampersand(): | |
| """Test sinfo filtering for DOWN&DRAIN states with an ampersand.""" | |
| # Change the state of some nodes to be both down and drained | |
| atf.run_command( | |
| f"scontrol update nodename={node_list[4]},{node_list[5]} state=down reason=test", | |
| user=atf.properties["slurm-user"], | |
| ) | |
| atf.run_command( | |
| f"scontrol update nodename={node_list[4]},{node_list[5]} state=drain reason=test", | |
| user=atf.properties["slurm-user"], | |
| ) | |
| # Get sinfo output for down&drained nodes | |
| output = atf.run_command_output( | |
| f"sinfo -Nh -n {node_range} -O NodeList,StateComplete --state='down&drain'" | |
| ) | |
| # Verify the output for nodes that are both down and drained (state will show as `drained`) | |
| lines = [line.strip().split() for line in output.strip().splitlines()] | |
| nodes = {node: status for node, status in lines} | |
| down_drained_nodes = [n for n, s in nodes.items() if "drain" in s] | |
| assert len(down_drained_nodes) == 2, "Expected 2 drained nodes" | |
| assert node_list[4] in down_drained_nodes, f"{node_list[4]} should be down&drained" | |
| assert node_list[5] in down_drained_nodes, f"{node_list[5]} should be down&drained" |