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

233 lines
7.0 KiB
Python

import os
import sys
import time
import pytest
from ray.autoscaler.v2.schema import (
AutoscalerInstance,
ClusterStatus,
IPPRGroupSpec,
IPPRStatus,
)
from ray.core.generated.autoscaler_pb2 import NodeState, NodeStatus
from ray.core.generated.instance_manager_pb2 import Instance
def test_cluster_status_default_stats():
status = ClusterStatus()
assert status.active_nodes == []
assert status.idle_nodes == []
assert status.pending_launches == []
assert status.failed_launches == []
assert status.pending_nodes == []
assert status.failed_nodes == []
assert status.cluster_resource_usage == []
assert status.stats.gcs_request_time_s == 0.0
assert status.stats.request_ts_s is None
def _make_ippr_status() -> IPPRStatus:
return IPPRStatus(
cloud_instance_id="ray-worker-1",
spec=IPPRGroupSpec(
min_cpu=1.0,
max_cpu=4.0,
min_memory=2,
max_memory=8,
resize_timeout=10,
),
current_cpu=1.0,
current_memory=2,
desired_cpu=1.0,
desired_memory=2,
)
def test_autoscaler_instance():
i = AutoscalerInstance()
assert not i.validate()[0], "Empty instance should be invalid"
i = AutoscalerInstance(im_instance=Instance(status=Instance.QUEUED))
assert i.validate()[0], "Instance with only im_instance should be valid"
i = AutoscalerInstance(
ray_node=NodeState(status=NodeStatus.RUNNING, instance_id="i-123"),
cloud_instance_id="i-123",
)
assert i.validate()[0], i.validate()[1]
i = AutoscalerInstance(
ray_node=NodeState(status=NodeStatus.RUNNING),
)
assert not i.validate()[0], "Missing cloud node id."
i = AutoscalerInstance(
im_instance=Instance(status=Instance.QUEUED),
ray_node=NodeState(status=NodeStatus.RUNNING),
)
assert not i.validate()[
0
], "cloud node id is required to link the ray node with im state"
i = AutoscalerInstance(
cloud_instance_id="i-123",
)
assert not i.validate()[
0
], "cloud instance id is not possible without im or ray node"
i = AutoscalerInstance(
im_instance=Instance(status=Instance.ALLOCATED, cloud_instance_id="i-123"),
cloud_instance_id="i-123",
)
assert i.validate()[0]
i = AutoscalerInstance(
im_instance=Instance(status=Instance.ALLOCATED, cloud_instance_id="i-123"),
cloud_instance_id="i-124", # mismatch.
)
assert not i.validate()[0], "cloud instance id should match"
i = AutoscalerInstance(
im_instance=Instance(status=Instance.QUEUED, cloud_instance_id="i-123"),
cloud_instance_id="i-123",
)
assert not i.validate()[0], "cloud instance id is not possible with queued state"
i = AutoscalerInstance(
im_instance=Instance(status=Instance.ALLOCATED, cloud_instance_id="i-123"),
)
assert not i.validate()[0], "cloud instance id should also be set"
i = AutoscalerInstance(
ray_node=NodeState(status=NodeStatus.RUNNING, instance_id="i-123"),
cloud_instance_id="i-123",
)
assert i.validate()[0]
i = AutoscalerInstance(
ray_node=NodeState(status=NodeStatus.RUNNING, instance_id="i-123"),
cloud_instance_id="i-124", # mismatch.
)
assert not i.validate()[0]
i = AutoscalerInstance(
im_instance=Instance(status=Instance.RAY_RUNNING, cloud_instance_id="i-123"),
ray_node=NodeState(status=NodeStatus.RUNNING, instance_id="i-123"),
cloud_instance_id="i-123",
)
assert i.validate()[0]
i = AutoscalerInstance(
im_instance=Instance(status=Instance.RAY_RUNNING, cloud_instance_id="i-123"),
ray_node=NodeState(status=NodeStatus.RUNNING, instance_id="i-123"),
cloud_instance_id="i-124", # mismatch.
)
assert not i.validate()[0]
def test_ippr_status_queue_resize_request():
status = _make_ippr_status()
status.resizing_at = 123
status.k8s_resize_status = "deferred"
status.k8s_resize_message = "pending"
status.raylet_id = "abc"
assert status.queue_resize_request(desired_cpu=2.0, desired_memory=4)
assert status.desired_cpu == 2.0
assert status.desired_memory == 4
assert status.resizing_at is None
assert status.k8s_resize_status == "new"
assert status.k8s_resize_message is None
assert not status.queue_resize_request(desired_cpu=2.0, desired_memory=4)
def test_ippr_status_request_and_progress_helpers():
status = _make_ippr_status()
assert status.has_resize_request_to_send() is False
assert status.is_in_progress() is False
status.raylet_id = "abc"
assert status.is_k8s_resize_finished()
assert not status.has_resize_request_to_send()
assert not status.is_in_progress()
status.queue_resize_request(desired_cpu=2.0)
assert status.has_resize_request_to_send()
assert status.is_in_progress()
assert not status.is_k8s_resize_finished()
status.resizing_at = int(time.time())
status.k8s_resize_status = None
assert status.is_in_progress()
assert status.is_k8s_resize_finished()
def test_ippr_status_need_sync_with_raylet():
status = _make_ippr_status()
assert status.need_sync_with_raylet() is False
status.raylet_id = "abc"
status.resizing_at = int(time.time())
status.k8s_resize_status = None
assert status.need_sync_with_raylet()
status.desired_cpu = 2.0
assert not status.need_sync_with_raylet()
def test_ippr_status_limits_and_can_resize_up():
status = _make_ippr_status()
assert status.can_resize_up()
status.raylet_id = "abc"
assert status.max_cpu() == 4.0
assert status.max_memory() == 8
assert status.can_resize_up()
status.suggested_max_cpu = 1.5
status.suggested_max_memory = 3
assert status.max_cpu() == 1.5
assert status.max_memory() == 3
status.current_cpu = 1.5
status.current_memory = 3
assert not status.can_resize_up()
status.current_cpu = 1.0
status.last_failed_at = 1
assert not status.can_resize_up()
def test_ippr_status_failure_and_timeout_helpers():
status = _make_ippr_status()
status.raylet_id = "abc"
status.desired_cpu = 2.0
status.desired_memory = 4
status.resizing_at = int(time.time()) - 20
assert status.is_timeout()
status.k8s_resize_status = "error"
assert status.is_errored()
status.record_failure("resize failed", failed_at=123)
assert status.last_failed_at == 123
assert status.last_failed_reason == "resize failed"
# K8s resize finished and resources match desired, but raylet sync is still
# pending: do not treat as timeout (provider will call GCS to sync).
status2 = _make_ippr_status()
status2.raylet_id = "abc"
status2.resizing_at = int(time.time()) - 20
status2.k8s_resize_status = None
assert status2.need_sync_with_raylet()
assert not status2.is_timeout()
if __name__ == "__main__":
if os.environ.get("PARALLEL_CI"):
sys.exit(pytest.main(["-n", "auto", "--boxed", "-vs", __file__]))
else:
sys.exit(pytest.main(["-sv", __file__]))