Files
2026-07-13 13:17:40 +08:00

188 lines
6.2 KiB
Python

import sys
import pytest
import ray
@ray.remote
class MyActor:
def __init__(self):
self.value = 0
def value(self):
return self.value
def get_node_id(self):
return ray.get_runtime_context().get_node_id()
@ray.remote
def get_node_id():
return ray.get_runtime_context().get_node_id()
@pytest.fixture
def cluster_with_labeled_nodes(ray_start_cluster):
cluster = ray_start_cluster
cluster.add_node(
resources={"worker1": 1},
num_cpus=3,
labels={"ray.io/accelerator-type": "A100", "region": "us-west4"},
)
ray.init(address=cluster.address)
node_1 = ray.get(get_node_id.options(resources={"worker1": 1}).remote())
cluster.add_node(
resources={"worker2": 1},
num_cpus=3,
labels={"ray.io/accelerator-type": "B200", "market-type": "spot"},
)
node_2 = ray.get(get_node_id.options(resources={"worker2": 1}).remote())
cluster.add_node(
resources={"worker3": 1},
num_cpus=3,
labels={
"ray.io/accelerator-type": "TPU",
"market-type": "on-demand",
"region": "us-east4",
},
)
node_3 = ray.get(get_node_id.options(resources={"worker3": 1}).remote())
cluster.wait_for_nodes()
return node_1, node_2, node_3
def test_label_selector_equals(cluster_with_labeled_nodes):
node_1, _, _ = cluster_with_labeled_nodes
actor = MyActor.options(label_selector={"ray.io/accelerator-type": "A100"}).remote()
assert ray.get(actor.get_node_id.remote(), timeout=3) == node_1
def test_label_selector_not_equals(cluster_with_labeled_nodes):
_, node_2, _ = cluster_with_labeled_nodes
actor = MyActor.options(
label_selector={"ray.io/accelerator-type": "!A100", "market-type": "spot"}
).remote()
assert ray.get(actor.get_node_id.remote(), timeout=3) == node_2
def test_label_selector_in(cluster_with_labeled_nodes):
node_1, _, _ = cluster_with_labeled_nodes
actor = MyActor.options(
label_selector={"region": "in(us-west4, us-central2)"}
).remote()
assert ray.get(actor.get_node_id.remote(), timeout=3) == node_1
def test_label_selector_not_in(cluster_with_labeled_nodes):
_, node_2, _ = cluster_with_labeled_nodes
actor = MyActor.options(
label_selector={
"ray.io/accelerator-type": "!in(A100)",
"region": "!in(us-east4, us-west4)",
}
).remote()
assert ray.get(actor.get_node_id.remote(), timeout=3) == node_2
def test_label_selector_multiple(cluster_with_labeled_nodes):
_, _, node_3 = cluster_with_labeled_nodes
actor = MyActor.options(
label_selector={"ray.io/accelerator-type": "TPU", "region": "us-east4"}
).remote()
assert ray.get(actor.get_node_id.remote(), timeout=3) == node_3
def test_fallback_strategy(cluster_with_labeled_nodes):
# Create a RayCluster with labelled nodes.
gpu_node, _, _ = cluster_with_labeled_nodes
# Define an unsatisfiable label selector.
infeasible_label_selector = {"ray.io/accelerator-type": "does-not-exist"}
# Create a fallback strategy with multiple accelerator options.
accelerator_fallbacks = [
{"label_selector": {"ray.io/accelerator-type": "A100"}},
{"label_selector": {"ray.io/accelerator-type": "TPU"}},
]
# Attempt to schedule the actor. The scheduler should fail to find a node with the
# primary `label_selector` and fall back to the first available option, 'A100'.
label_selector_actor = MyActor.options(
label_selector=infeasible_label_selector,
fallback_strategy=accelerator_fallbacks,
).remote()
# Assert that the actor was scheduled on the expected node.
assert ray.get(label_selector_actor.get_node_id.remote(), timeout=5) == gpu_node
def test_empty_selector_fallback_strategy(cluster_with_labeled_nodes):
node_1, node_2, node_3 = cluster_with_labeled_nodes
# Define an unsatisfiable label selector.
infeasible_label_selector = {"ray.io/accelerator-type": "does-not-exist"}
# Create a fallback strategy with multiple label selector fallbacks. The
# first fallback option is unsatisfiable, so it falls back to the empty label
# selector option. This fallback should match any node.
accelerator_fallbacks = [
{"label_selector": {"ray.io/accelerator-type": "also-does-not-exist"}},
{"label_selector": {}},
]
label_selector_actor = MyActor.options(
label_selector=infeasible_label_selector,
fallback_strategy=accelerator_fallbacks,
).remote()
# Assert that the actor was scheduled on the expected node.
assert ray.get(label_selector_actor.get_node_id.remote(), timeout=5) in {
node_1,
node_2,
node_3,
}
def test_infeasible_fallback_strategy(cluster_with_labeled_nodes):
# Define an unsatisfiable label selector and fallback strategy.
label_selector = {"ray.io/accelerator-type": "does-not-exist"}
fallback_strategy = [
{"label_selector": {"ray.io/accelerator-type": "does-not-exist-either"}},
{"label_selector": {"ray.io/accelerator-type": "also-nonexistant"}},
]
# Attempt to schedule the actor, but it should timeout since none of
# the nodes match any label selector.
label_selector_actor = MyActor.options(
label_selector=label_selector, fallback_strategy=fallback_strategy
).remote()
with pytest.raises(TimeoutError):
ray.get(label_selector_actor.get_node_id.remote(), timeout=3)
def test_fallback_with_feasible_primary_selector(cluster_with_labeled_nodes):
gpu_node, _, _ = cluster_with_labeled_nodes
feasible_label_selector = {"ray.io/accelerator-type": "A100"}
feasible_fallback_strategy = [
{"label_selector": {"ray.io/accelerator-type": "B200"}},
]
# Attempt to schedule the actor. The scheduler should use the
# primary selector and ignore the fallback.
label_selector_actor = MyActor.options(
label_selector=feasible_label_selector,
fallback_strategy=feasible_fallback_strategy,
).remote()
# Assert that the actor was scheduled on the expected node and not the fallback.
assert ray.get(label_selector_actor.get_node_id.remote(), timeout=5) == gpu_node
if __name__ == "__main__":
sys.exit(pytest.main(["-sv", __file__]))