300 lines
9.5 KiB
Python
300 lines
9.5 KiB
Python
import pytest
|
|
|
|
from ray.serve._private.common import (
|
|
DeploymentID,
|
|
ReplicaID,
|
|
RequestProtocol,
|
|
RunningReplicaInfo,
|
|
)
|
|
from ray.serve._private.long_poll import LongPollNamespace
|
|
from ray.serve.schema import (
|
|
Target,
|
|
TargetGroup,
|
|
)
|
|
from ray.serve.tests.unit.test_controller_direct_ingress import (
|
|
FakeApplicationStateManager,
|
|
FakeDeploymentStateManager,
|
|
FakeDirectIngressController,
|
|
FakeKVStore,
|
|
FakeLongPollHost,
|
|
FakeProxyStateManager,
|
|
)
|
|
|
|
|
|
# Test Controller that overrides methods and dependencies for HAProxy testing
|
|
class FakeHAProxyController(FakeDirectIngressController):
|
|
def __init__(
|
|
self,
|
|
kv_store,
|
|
long_poll_host,
|
|
application_state_manager,
|
|
deployment_state_manager,
|
|
proxy_state_manager,
|
|
):
|
|
super().__init__(
|
|
kv_store=kv_store,
|
|
long_poll_host=long_poll_host,
|
|
application_state_manager=application_state_manager,
|
|
deployment_state_manager=deployment_state_manager,
|
|
proxy_state_manager=proxy_state_manager,
|
|
)
|
|
|
|
self._ha_proxy_enabled = True
|
|
|
|
|
|
@pytest.fixture
|
|
def haproxy_controller():
|
|
kv_store = FakeKVStore()
|
|
long_poll_host = FakeLongPollHost()
|
|
app_state_manager = FakeApplicationStateManager({}, {}, {})
|
|
deployment_state_manager = FakeDeploymentStateManager({})
|
|
proxy_state_manager = FakeProxyStateManager()
|
|
|
|
controller = FakeHAProxyController(
|
|
kv_store=kv_store,
|
|
long_poll_host=long_poll_host,
|
|
application_state_manager=app_state_manager,
|
|
deployment_state_manager=deployment_state_manager,
|
|
proxy_state_manager=proxy_state_manager,
|
|
)
|
|
|
|
yield controller
|
|
|
|
|
|
@pytest.mark.parametrize("from_proxy_manager", [True, False])
|
|
@pytest.mark.parametrize("ha_proxy_enabled", [True, False])
|
|
def test_get_target_groups_haproxy(
|
|
haproxy_controller: FakeHAProxyController,
|
|
from_proxy_manager: bool,
|
|
ha_proxy_enabled: bool,
|
|
):
|
|
"""Tests get_target_groups returns the appropriate target groups based on the
|
|
ha_proxy_enabled and from_proxy_manager parameters."""
|
|
|
|
haproxy_controller._ha_proxy_enabled = ha_proxy_enabled
|
|
|
|
# Setup test data with running applications
|
|
app_statuses = {"app1": {}}
|
|
route_prefixes = {"app1": "/app1"}
|
|
ingress_deployments = {"app1": "app1_ingress"}
|
|
|
|
deployment_id1 = DeploymentID(name="app1_ingress", app_name="app1")
|
|
|
|
# Create replica info
|
|
replica_id1 = ReplicaID(unique_id="replica1", deployment_id=deployment_id1)
|
|
replica_info1 = RunningReplicaInfo(
|
|
replica_id=replica_id1,
|
|
node_id="node1",
|
|
node_ip="10.0.0.1",
|
|
availability_zone="az1",
|
|
actor_name="replica1",
|
|
max_ongoing_requests=100,
|
|
)
|
|
|
|
running_replica_infos = {deployment_id1: [replica_info1]}
|
|
|
|
# Setup test application state manager
|
|
haproxy_controller.application_state_manager = FakeApplicationStateManager(
|
|
app_statuses=app_statuses,
|
|
route_prefixes=route_prefixes,
|
|
ingress_deployments=ingress_deployments,
|
|
)
|
|
|
|
# Setup test deployment state manager
|
|
haproxy_controller.deployment_state_manager = FakeDeploymentStateManager(
|
|
running_replica_infos=running_replica_infos,
|
|
)
|
|
|
|
# Setup proxy state manager
|
|
haproxy_controller.proxy_state_manager.add_proxy_details(
|
|
"proxy_node1", "10.0.1.1", "proxy1"
|
|
)
|
|
haproxy_controller.proxy_state_manager.add_proxy_details(
|
|
"proxy_node2", "10.0.1.2", "proxy2"
|
|
)
|
|
|
|
# Allocate ports for replicas using controller's methods
|
|
http_port1 = haproxy_controller.allocate_replica_port(
|
|
"node1", replica_id1.unique_id, RequestProtocol.HTTP
|
|
)
|
|
grpc_port1 = haproxy_controller.allocate_replica_port(
|
|
"node1", replica_id1.unique_id, RequestProtocol.GRPC
|
|
)
|
|
|
|
target_groups = haproxy_controller.get_target_groups(
|
|
from_proxy_manager=from_proxy_manager
|
|
)
|
|
|
|
# Create expected target groups
|
|
if ha_proxy_enabled and not from_proxy_manager:
|
|
expected_target_groups = [
|
|
TargetGroup(
|
|
protocol=RequestProtocol.HTTP,
|
|
route_prefix="/",
|
|
app_name="",
|
|
targets=[
|
|
Target(ip="10.0.1.1", port=8000, instance_id="", name="proxy1"),
|
|
Target(ip="10.0.1.2", port=8000, instance_id="", name="proxy2"),
|
|
],
|
|
),
|
|
TargetGroup(
|
|
protocol=RequestProtocol.GRPC,
|
|
route_prefix="/",
|
|
app_name="",
|
|
targets=[
|
|
Target(ip="10.0.1.1", port=9000, instance_id="", name="proxy1"),
|
|
Target(ip="10.0.1.2", port=9000, instance_id="", name="proxy2"),
|
|
],
|
|
),
|
|
]
|
|
else:
|
|
expected_target_groups = [
|
|
TargetGroup(
|
|
protocol=RequestProtocol.HTTP,
|
|
route_prefix="/app1",
|
|
app_name="app1",
|
|
targets=[
|
|
Target(
|
|
ip="10.0.0.1", port=http_port1, instance_id="", name="replica1"
|
|
),
|
|
],
|
|
),
|
|
TargetGroup(
|
|
protocol=RequestProtocol.GRPC,
|
|
route_prefix="/app1",
|
|
app_name="app1",
|
|
targets=[
|
|
Target(
|
|
ip="10.0.0.1", port=grpc_port1, instance_id="", name="replica1"
|
|
),
|
|
],
|
|
),
|
|
]
|
|
|
|
# Sort both lists to ensure consistent comparison
|
|
target_groups.sort(key=lambda g: (g.protocol, g.route_prefix))
|
|
expected_target_groups.sort(key=lambda g: (g.protocol, g.route_prefix))
|
|
|
|
assert target_groups == expected_target_groups
|
|
|
|
|
|
def test_get_target_groups_app_with_no_running_replicas(
|
|
haproxy_controller: FakeHAProxyController,
|
|
):
|
|
"""Tests get_target_groups returns the appropriate target groups when an app
|
|
has no running replicas."""
|
|
|
|
# Setup test data with running applications
|
|
app_statuses = {"app1": {}}
|
|
route_prefixes = {"app1": "/app1"}
|
|
ingress_deployments = {"app1": "app1_ingress"}
|
|
|
|
deployment_id1 = DeploymentID(name="app1_ingress", app_name="app1")
|
|
|
|
running_replica_infos = {deployment_id1: []}
|
|
|
|
# Setup test application state manager
|
|
haproxy_controller.application_state_manager = FakeApplicationStateManager(
|
|
app_statuses=app_statuses,
|
|
route_prefixes=route_prefixes,
|
|
ingress_deployments=ingress_deployments,
|
|
)
|
|
|
|
# Setup test deployment state manager
|
|
haproxy_controller.deployment_state_manager = FakeDeploymentStateManager(
|
|
running_replica_infos=running_replica_infos,
|
|
)
|
|
|
|
target_groups = haproxy_controller.get_target_groups(
|
|
from_proxy_manager=True,
|
|
)
|
|
|
|
assert target_groups == [
|
|
TargetGroup(
|
|
protocol=RequestProtocol.HTTP,
|
|
route_prefix="/app1",
|
|
app_name="app1",
|
|
targets=[],
|
|
),
|
|
TargetGroup(
|
|
protocol=RequestProtocol.GRPC,
|
|
route_prefix="/app1",
|
|
app_name="app1",
|
|
targets=[],
|
|
),
|
|
]
|
|
|
|
|
|
def test_broadcast_fallback_targets(
|
|
haproxy_controller: FakeHAProxyController,
|
|
):
|
|
"""Tests get_fallback_proxy_targets returns the appropriate fallback proxy targets."""
|
|
|
|
lph = haproxy_controller.long_poll_host
|
|
assert LongPollNamespace.FALLBACK_TARGETS not in lph.notified_changes
|
|
|
|
# Ensure there's no broadcast
|
|
haproxy_controller.broadcast_fallback_targets_if_changed()
|
|
assert LongPollNamespace.FALLBACK_TARGETS not in lph.notified_changes
|
|
|
|
# Add fallback proxy target
|
|
haproxy_controller.proxy_state_manager.add_fallback_proxy_target(
|
|
node_ip="10.0.0.1",
|
|
port=8500,
|
|
node_instance_id="instance1",
|
|
actor_name="proxy1",
|
|
protocol=RequestProtocol.HTTP,
|
|
)
|
|
|
|
# Ensure there is a broadcast
|
|
haproxy_controller.broadcast_fallback_targets_if_changed()
|
|
assert LongPollNamespace.FALLBACK_TARGETS in lph.notified_changes
|
|
assert lph.notified_changes[LongPollNamespace.FALLBACK_TARGETS] == {
|
|
RequestProtocol.HTTP: Target(
|
|
ip="10.0.0.1", port=8500, instance_id="instance1", name="proxy1"
|
|
),
|
|
}
|
|
|
|
# Change the fallback proxy target
|
|
haproxy_controller.proxy_state_manager.add_fallback_proxy_target(
|
|
node_ip="10.0.0.2",
|
|
port=8500,
|
|
node_instance_id="instance1",
|
|
actor_name="proxy1",
|
|
protocol=RequestProtocol.HTTP,
|
|
)
|
|
|
|
# Ensure there is a broadcast
|
|
haproxy_controller.broadcast_fallback_targets_if_changed()
|
|
assert LongPollNamespace.FALLBACK_TARGETS in lph.notified_changes
|
|
assert lph.notified_changes[LongPollNamespace.FALLBACK_TARGETS] == {
|
|
RequestProtocol.HTTP: Target(
|
|
ip="10.0.0.2", port=8500, instance_id="instance1", name="proxy1"
|
|
),
|
|
}
|
|
|
|
# Add fallback proxy target
|
|
haproxy_controller.proxy_state_manager.add_fallback_proxy_target(
|
|
node_ip="10.0.0.1",
|
|
port=9500,
|
|
node_instance_id="instance1",
|
|
actor_name="proxy1",
|
|
protocol=RequestProtocol.GRPC,
|
|
)
|
|
|
|
# Ensure there's another broadcast
|
|
haproxy_controller.broadcast_fallback_targets_if_changed()
|
|
assert LongPollNamespace.FALLBACK_TARGETS in lph.notified_changes
|
|
assert lph.notified_changes[LongPollNamespace.FALLBACK_TARGETS] == {
|
|
RequestProtocol.HTTP: Target(
|
|
ip="10.0.0.2", port=8500, instance_id="instance1", name="proxy1"
|
|
),
|
|
RequestProtocol.GRPC: Target(
|
|
ip="10.0.0.1", port=9500, instance_id="instance1", name="proxy1"
|
|
),
|
|
}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main()
|