chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,462 @@
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
import yaml
|
||||
from fastapi import FastAPI
|
||||
|
||||
import ray
|
||||
from ray import serve
|
||||
from ray._common.test_utils import wait_for_condition
|
||||
from ray._common.usage.usage_lib import get_extra_usage_tags_to_report
|
||||
from ray.serve._private.constants import (
|
||||
RAY_SERVE_USE_GRPC_BY_DEFAULT,
|
||||
SERVE_MULTIPLEXED_MODEL_ID,
|
||||
)
|
||||
from ray.serve._private.test_utils import (
|
||||
check_apps_running,
|
||||
check_telemetry,
|
||||
)
|
||||
from ray.serve._private.usage import ServeUsageTag
|
||||
from ray.serve.context import _get_global_client
|
||||
from ray.serve.schema import ApplicationStatus, ServeDeploySchema
|
||||
|
||||
|
||||
def test_fastapi_detected(manage_ray_with_telemetry):
|
||||
"""
|
||||
Check that FastAPI is detected by telemetry.
|
||||
"""
|
||||
|
||||
storage_handle = manage_ray_with_telemetry
|
||||
|
||||
# Check that telemetry related to FastAPI app is not set
|
||||
def check_report_before_fastapi():
|
||||
report = ray.get(storage_handle.get_report.remote())
|
||||
assert ServeUsageTag.FASTAPI_USED.get_value_from_report(report) is None
|
||||
assert ServeUsageTag.API_VERSION.get_value_from_report(report) == "v2"
|
||||
assert int(ServeUsageTag.NUM_APPS.get_value_from_report(report)) == 1
|
||||
assert int(ServeUsageTag.NUM_DEPLOYMENTS.get_value_from_report(report)) == 1
|
||||
assert int(ServeUsageTag.NUM_GPU_DEPLOYMENTS.get_value_from_report(report)) == 0
|
||||
return True
|
||||
|
||||
wait_for_condition(check_report_before_fastapi)
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@serve.deployment
|
||||
@serve.ingress(app)
|
||||
class FastAPIDeployment:
|
||||
@app.get("/route1")
|
||||
async def route1(self):
|
||||
return "Welcome to route 1!"
|
||||
|
||||
@app.get("/route2")
|
||||
async def app2(self):
|
||||
return "Welcome to route 2!"
|
||||
|
||||
fastapi_app = FastAPIDeployment.bind()
|
||||
serve.run(fastapi_app, name="fastapi_app", route_prefix="/fastapi")
|
||||
|
||||
wait_for_condition(
|
||||
lambda: serve.status().applications["fastapi_app"].status
|
||||
== ApplicationStatus.RUNNING,
|
||||
timeout=15,
|
||||
)
|
||||
|
||||
current_num_reports = ray.get(storage_handle.get_reports_received.remote())
|
||||
|
||||
wait_for_condition(
|
||||
lambda: ray.get(storage_handle.get_reports_received.remote())
|
||||
> current_num_reports,
|
||||
timeout=5,
|
||||
)
|
||||
report = ray.get(storage_handle.get_report.remote())
|
||||
|
||||
# Check all telemetry relevant to the Serve apps on this cluster.
|
||||
assert int(ServeUsageTag.FASTAPI_USED.get_value_from_report(report)) == 1
|
||||
assert ServeUsageTag.API_VERSION.get_value_from_report(report) == "v2"
|
||||
assert int(ServeUsageTag.NUM_APPS.get_value_from_report(report)) == 2
|
||||
assert int(ServeUsageTag.NUM_DEPLOYMENTS.get_value_from_report(report)) == 2
|
||||
assert int(ServeUsageTag.NUM_GPU_DEPLOYMENTS.get_value_from_report(report)) == 0
|
||||
|
||||
# Check that Serve telemetry not relevant to the running apps is omitted.
|
||||
assert ServeUsageTag.DAG_DRIVER_USED.get_value_from_report(report) is None
|
||||
assert ServeUsageTag.HTTP_ADAPTER_USED.get_value_from_report(report) is None
|
||||
assert ServeUsageTag.GRPC_INGRESS_USED.get_value_from_report(report) is None
|
||||
assert ServeUsageTag.REST_API_VERSION.get_value_from_report(report) is None
|
||||
|
||||
|
||||
@serve.deployment
|
||||
class Stub:
|
||||
pass
|
||||
|
||||
|
||||
stub_app = Stub.bind()
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform == "win32", reason="File path incorrect on Windows.")
|
||||
def test_rest_api(manage_ray_with_telemetry, tmp_dir):
|
||||
"""
|
||||
Check that telemetry works with REST API.
|
||||
"""
|
||||
|
||||
storage = manage_ray_with_telemetry
|
||||
|
||||
# Check that REST API telemetry is not set
|
||||
check_telemetry(ServeUsageTag.REST_API_VERSION, expected=None)
|
||||
|
||||
config = {
|
||||
"applications": [
|
||||
{
|
||||
"name": "stub_app",
|
||||
"import_path": "ray.serve.tests.test_telemetry_1.stub_app",
|
||||
"route_prefix": "/stub",
|
||||
},
|
||||
]
|
||||
}
|
||||
config_file_path = f"{tmp_dir}/config.yaml"
|
||||
with open(config_file_path, "w+") as f:
|
||||
yaml.safe_dump(config, f)
|
||||
|
||||
subprocess.check_output(["serve", "deploy", config_file_path])
|
||||
|
||||
client = _get_global_client()
|
||||
# Make sure the applications are RUNNING.
|
||||
wait_for_condition(check_apps_running, apps=["stub_app"], timeout=15)
|
||||
|
||||
current_num_reports = ray.get(storage.get_reports_received.remote())
|
||||
|
||||
wait_for_condition(
|
||||
lambda: ray.get(storage.get_reports_received.remote()) > current_num_reports,
|
||||
timeout=5,
|
||||
)
|
||||
report = ray.get(storage.get_report.remote())
|
||||
|
||||
# Check all telemetry relevant to the Serve apps on this cluster
|
||||
assert ServeUsageTag.REST_API_VERSION.get_value_from_report(report) == "v2"
|
||||
assert ServeUsageTag.API_VERSION.get_value_from_report(report) == "v2"
|
||||
assert int(ServeUsageTag.NUM_GPU_DEPLOYMENTS.get_value_from_report(report)) == 0
|
||||
|
||||
# Assert num of deployments from controller.
|
||||
assert len(client.get_all_deployment_statuses()) == 2
|
||||
assert int(ServeUsageTag.NUM_APPS.get_value_from_report(report)) == 2
|
||||
assert int(ServeUsageTag.NUM_DEPLOYMENTS.get_value_from_report(report)) == 2
|
||||
|
||||
# Check that Serve telemetry not relevant to the running apps is omitted
|
||||
assert ServeUsageTag.FASTAPI_USED.get_value_from_report(report) is None
|
||||
assert ServeUsageTag.GRPC_INGRESS_USED.get_value_from_report(report) is None
|
||||
assert ServeUsageTag.HTTP_ADAPTER_USED.get_value_from_report(report) is None
|
||||
assert ServeUsageTag.DAG_DRIVER_USED.get_value_from_report(report) is None
|
||||
assert ServeUsageTag.AUTO_NUM_REPLICAS_USED.get_value_from_report(report) is None
|
||||
|
||||
# Check that app deletions are tracked.
|
||||
new_config = {"applications": []}
|
||||
|
||||
with open(config_file_path, "w+") as f:
|
||||
yaml.safe_dump(new_config, f)
|
||||
|
||||
subprocess.check_output(["serve", "deploy", config_file_path])
|
||||
|
||||
wait_for_condition(
|
||||
lambda: int(
|
||||
ServeUsageTag.NUM_APPS.get_value_from_report(
|
||||
ray.get(storage.get_report.remote())
|
||||
)
|
||||
)
|
||||
== 1,
|
||||
timeout=15,
|
||||
)
|
||||
report = ray.get(storage.get_report.remote())
|
||||
assert int(ServeUsageTag.NUM_APPS.get_value_from_report(report)) == 1
|
||||
assert int(ServeUsageTag.NUM_DEPLOYMENTS.get_value_from_report(report)) == 1
|
||||
|
||||
|
||||
@serve.deployment(ray_actor_options={"num_cpus": 0})
|
||||
class Tester:
|
||||
def __call__(self, *args):
|
||||
pass
|
||||
|
||||
def reconfigure(self, *args):
|
||||
pass
|
||||
|
||||
|
||||
tester = Tester.bind()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"lightweight_option,value,new_value",
|
||||
[
|
||||
("num_replicas", 1, 2),
|
||||
("user_config", {}, {"some_setting": 10}),
|
||||
("autoscaling_config", {"max_replicas": 3}, {"max_replicas": 5}),
|
||||
],
|
||||
)
|
||||
def test_lightweight_config_options(
|
||||
manage_ray_with_telemetry, lightweight_option, value, new_value
|
||||
):
|
||||
"""Check that lightweight config options are detected by telemetry."""
|
||||
|
||||
storage = manage_ray_with_telemetry
|
||||
|
||||
lightweight_tagkeys = {
|
||||
"num_replicas": ServeUsageTag.NUM_REPLICAS_LIGHTWEIGHT_UPDATED,
|
||||
"user_config": ServeUsageTag.USER_CONFIG_LIGHTWEIGHT_UPDATED,
|
||||
"autoscaling_config": ServeUsageTag.AUTOSCALING_CONFIG_LIGHTWEIGHT_UPDATED,
|
||||
}
|
||||
|
||||
# Check that REST API telemetry is not set
|
||||
check_telemetry(ServeUsageTag.REST_API_VERSION, expected=None)
|
||||
|
||||
config = {
|
||||
"applications": [
|
||||
{
|
||||
"name": "test_app",
|
||||
"import_path": "ray.serve.tests.test_telemetry_1.tester",
|
||||
"deployments": [{"name": "Tester"}],
|
||||
},
|
||||
]
|
||||
}
|
||||
config["applications"][0]["deployments"][0][lightweight_option] = value
|
||||
|
||||
# Deploy first config
|
||||
client = _get_global_client()
|
||||
client.deploy_apps(ServeDeploySchema(**config))
|
||||
wait_for_condition(check_apps_running, apps=["test_app"], timeout=15)
|
||||
|
||||
current_num_reports = ray.get(storage.get_reports_received.remote())
|
||||
|
||||
wait_for_condition(
|
||||
lambda: ray.get(storage.get_reports_received.remote()) > current_num_reports,
|
||||
timeout=10,
|
||||
)
|
||||
report = ray.get(storage.get_report.remote())
|
||||
|
||||
# Check
|
||||
assert int(ServeUsageTag.NUM_APPS.get_value_from_report(report)) == 2
|
||||
assert ServeUsageTag.API_VERSION.get_value_from_report(report) == "v2"
|
||||
for tagkey in lightweight_tagkeys.values():
|
||||
assert tagkey.get_value_from_report(report) is None
|
||||
|
||||
# Change config and deploy again
|
||||
config["applications"][0]["deployments"][0][lightweight_option] = new_value
|
||||
client.deploy_apps(ServeDeploySchema(**config))
|
||||
wait_for_condition(check_apps_running, apps=["test_app"], timeout=15)
|
||||
|
||||
# Check again
|
||||
wait_for_condition(
|
||||
check_telemetry,
|
||||
tag=lightweight_tagkeys[lightweight_option],
|
||||
expected="True",
|
||||
timeout=5,
|
||||
)
|
||||
report = ray.get(storage.get_report.remote())
|
||||
assert int(ServeUsageTag.NUM_APPS.get_value_from_report(report)) == 2
|
||||
assert ServeUsageTag.API_VERSION.get_value_from_report(report) == "v2"
|
||||
for tagkey in lightweight_tagkeys.values():
|
||||
if tagkey != lightweight_tagkeys[lightweight_option]:
|
||||
assert tagkey.get_value_from_report(report) is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("call_in_deployment", [False, True])
|
||||
def test_handle_apis_detected(manage_ray_with_telemetry, call_in_deployment):
|
||||
"""Check that the various handles are detected correctly by telemetry."""
|
||||
|
||||
check_telemetry(ServeUsageTag.DEPLOYMENT_HANDLE_API_USED, expected=None)
|
||||
|
||||
@serve.deployment
|
||||
class Downstream:
|
||||
def __call__(self):
|
||||
return "hi"
|
||||
|
||||
@serve.deployment
|
||||
class Caller:
|
||||
def __init__(self, h):
|
||||
self._h = h
|
||||
|
||||
async def __call__(self, call_downstream=True):
|
||||
if call_downstream:
|
||||
await self._h.remote()
|
||||
return "ok"
|
||||
|
||||
handle = serve.run(Caller.bind(Downstream.bind()))
|
||||
|
||||
if call_in_deployment:
|
||||
result = httpx.get("http://localhost:8000/").text
|
||||
else:
|
||||
result = handle.remote(call_downstream=False).result()
|
||||
|
||||
assert result == "ok"
|
||||
|
||||
wait_for_condition(
|
||||
check_telemetry, tag=ServeUsageTag.DEPLOYMENT_HANDLE_API_USED, expected="1"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
RAY_SERVE_USE_GRPC_BY_DEFAULT,
|
||||
reason="cannot convert to object ref when using gRPC",
|
||||
)
|
||||
@pytest.mark.parametrize("mode", ["http", "outside_deployment", "inside_deployment"])
|
||||
def test_deployment_handle_to_obj_ref_detected(manage_ray_with_telemetry, mode):
|
||||
"""Check that the handle to_object_ref API is detected correctly by telemetry."""
|
||||
|
||||
check_telemetry(
|
||||
ServeUsageTag.DEPLOYMENT_HANDLE_TO_OBJECT_REF_API_USED, expected=None
|
||||
)
|
||||
|
||||
@serve.deployment
|
||||
class Downstream:
|
||||
def __call__(self):
|
||||
return "hi"
|
||||
|
||||
@serve.deployment
|
||||
class Caller:
|
||||
def __init__(self, h):
|
||||
self._h = h
|
||||
|
||||
async def get(self, call_downstream=False):
|
||||
if call_downstream:
|
||||
await self._h.remote()._to_object_ref()
|
||||
return "ok"
|
||||
|
||||
async def __call__(self):
|
||||
return await self.get()
|
||||
|
||||
handle = serve.run(Caller.bind(Downstream.bind()))
|
||||
|
||||
if mode == "http":
|
||||
result = httpx.get("http://localhost:8000/").text
|
||||
elif mode == "outside_deployment":
|
||||
result = ray.get(handle.get.remote()._to_object_ref_sync())
|
||||
else:
|
||||
result = handle.get.remote(call_downstream=True).result()
|
||||
|
||||
assert result == "ok"
|
||||
|
||||
if mode == "http":
|
||||
for _ in range(20):
|
||||
check_telemetry(
|
||||
ServeUsageTag.DEPLOYMENT_HANDLE_TO_OBJECT_REF_API_USED, expected=None
|
||||
)
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
wait_for_condition(
|
||||
check_telemetry,
|
||||
tag=ServeUsageTag.DEPLOYMENT_HANDLE_TO_OBJECT_REF_API_USED,
|
||||
expected="1",
|
||||
)
|
||||
|
||||
|
||||
def test_multiplexed_detect(manage_ray_with_telemetry):
|
||||
"""Check that multiplexed api is detected by telemetry."""
|
||||
|
||||
@serve.deployment
|
||||
class Model:
|
||||
@serve.multiplexed(max_num_models_per_replica=1)
|
||||
async def get_model(self, tag):
|
||||
return tag
|
||||
|
||||
async def __call__(self, request):
|
||||
tag = serve.get_multiplexed_model_id()
|
||||
await self.get_model(tag)
|
||||
return tag
|
||||
|
||||
serve.run(Model.bind(), name="app", route_prefix="/app")
|
||||
wait_for_condition(check_apps_running, apps=["app"])
|
||||
|
||||
check_telemetry(ServeUsageTag.MULTIPLEXED_API_USED, expected=None)
|
||||
|
||||
headers = {SERVE_MULTIPLEXED_MODEL_ID: "1"}
|
||||
resp = httpx.get("http://localhost:8000/app", headers=headers)
|
||||
assert resp.status_code == 200
|
||||
|
||||
wait_for_condition(
|
||||
check_telemetry, tag=ServeUsageTag.MULTIPLEXED_API_USED, expected="1"
|
||||
)
|
||||
|
||||
|
||||
class TestProxyTelemetry:
|
||||
def test_both_proxies_detected(manage_ray, ray_shutdown):
|
||||
"""Test that both HTTP and gRPC proxies are detected by telemetry.
|
||||
|
||||
When both HTTP and gRPC proxies are used, both telemetry should be detected.
|
||||
"""
|
||||
result = get_extra_usage_tags_to_report(
|
||||
ray.experimental.internal_kv.internal_kv_get_gcs_client()
|
||||
)
|
||||
report = {"extra_usage_tags": result}
|
||||
|
||||
# Ensure neither the HTTP nor gRPC proxy telemetry exist.
|
||||
assert ServeUsageTag.HTTP_PROXY_USED.get_value_from_report(report) is None
|
||||
assert ServeUsageTag.GRPC_PROXY_USED.get_value_from_report(report) is None
|
||||
|
||||
grpc_servicer_functions = [
|
||||
"ray.serve.generated.serve_pb2_grpc."
|
||||
"add_UserDefinedServiceServicer_to_server",
|
||||
]
|
||||
serve.start(grpc_options={"grpc_servicer_functions": grpc_servicer_functions})
|
||||
|
||||
result = get_extra_usage_tags_to_report(
|
||||
ray.experimental.internal_kv.internal_kv_get_gcs_client()
|
||||
)
|
||||
report = {"extra_usage_tags": result}
|
||||
|
||||
# Ensure both HTTP and gRPC proxy telemetry exist.
|
||||
assert int(ServeUsageTag.HTTP_PROXY_USED.get_value_from_report(report)) == 1
|
||||
assert int(ServeUsageTag.GRPC_PROXY_USED.get_value_from_report(report)) == 1
|
||||
|
||||
def test_only_http_proxy_detected(manage_ray, ray_shutdown):
|
||||
"""Test that only HTTP proxy is detected by telemetry.
|
||||
|
||||
When only HTTP proxy is used, only the http proxy telemetry should be detected.
|
||||
"""
|
||||
result = get_extra_usage_tags_to_report(
|
||||
ray.experimental.internal_kv.internal_kv_get_gcs_client()
|
||||
)
|
||||
report = {"extra_usage_tags": result}
|
||||
|
||||
# Ensure the telemetry does not yet exist.
|
||||
assert ServeUsageTag.HTTP_PROXY_USED.get_value_from_report(report) is None
|
||||
assert ServeUsageTag.GRPC_PROXY_USED.get_value_from_report(report) is None
|
||||
|
||||
serve.start()
|
||||
|
||||
result = get_extra_usage_tags_to_report(
|
||||
ray.experimental.internal_kv.internal_kv_get_gcs_client()
|
||||
)
|
||||
report = {"extra_usage_tags": result}
|
||||
|
||||
# Ensure only the HTTP proxy telemetry exist.
|
||||
assert int(ServeUsageTag.HTTP_PROXY_USED.get_value_from_report(report)) == 1
|
||||
assert ServeUsageTag.GRPC_PROXY_USED.get_value_from_report(report) is None
|
||||
|
||||
def test_no_proxy_detected(manage_ray, ray_shutdown):
|
||||
"""Test that no proxy is detected by telemetry.
|
||||
|
||||
When neither HTTP nor gRPC proxy is used, no proxy telemetry should be detected.
|
||||
"""
|
||||
result = get_extra_usage_tags_to_report(
|
||||
ray.experimental.internal_kv.internal_kv_get_gcs_client()
|
||||
)
|
||||
report = {"extra_usage_tags": result}
|
||||
|
||||
# Ensure neither the HTTP nor gRPC proxy telemetry exist.
|
||||
assert ServeUsageTag.HTTP_PROXY_USED.get_value_from_report(report) is None
|
||||
assert ServeUsageTag.GRPC_PROXY_USED.get_value_from_report(report) is None
|
||||
|
||||
serve.start(http_options={"location": "NoServer"})
|
||||
|
||||
result = get_extra_usage_tags_to_report(
|
||||
ray.experimental.internal_kv.internal_kv_get_gcs_client()
|
||||
)
|
||||
report = {"extra_usage_tags": result}
|
||||
|
||||
# Ensure neither the HTTP nor gRPC proxy telemetry exist.
|
||||
assert ServeUsageTag.HTTP_PROXY_USED.get_value_from_report(report) is None
|
||||
assert ServeUsageTag.GRPC_PROXY_USED.get_value_from_report(report) is None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main(["-v", "-s", __file__]))
|
||||
Reference in New Issue
Block a user