183 lines
5.2 KiB
Python
183 lines
5.2 KiB
Python
import os
|
|
import signal
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
import requests
|
|
|
|
from ray import serve
|
|
from ray._common.test_utils import wait_for_condition
|
|
from ray.serve._private.constants import SERVE_DEFAULT_APP_NAME
|
|
from ray.serve.schema import ApplicationStatus, LoggingConfig
|
|
from ray.serve.tests.conftest import * # noqa
|
|
from ray.serve.tests.conftest import _shared_serve_instance # noqa
|
|
|
|
|
|
@serve.deployment
|
|
class Downstream:
|
|
def __call__(self):
|
|
return "hi"
|
|
|
|
|
|
downstream_node = Downstream.bind()
|
|
|
|
|
|
@serve.deployment
|
|
class Ingress:
|
|
def __init__(self, handle, **handle_options):
|
|
self._handle = handle
|
|
self._handle._init(**handle_options)
|
|
|
|
async def __call__(self):
|
|
return await self._handle.options(_by_reference=False).remote()
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
sys.platform == "win32", reason="Temp directory cleanup fails on Windows"
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"ray_instance",
|
|
[{"RAY_SERVE_LOG_TO_STDERR": "1"}],
|
|
indirect=True,
|
|
)
|
|
def test_no_spammy_errors_in_composed_app(ray_instance, tmp_dir):
|
|
"""Direct all stdout/stderr to logs, and check that the false errors
|
|
from gRPC are not there in replica logs."""
|
|
|
|
logs_dir = Path(tmp_dir)
|
|
logging_config = LoggingConfig(encoding="JSON", logs_dir=str(logs_dir))
|
|
|
|
h = serve.run(
|
|
Ingress.options(logging_config=logging_config).bind(
|
|
Downstream.options(logging_config=logging_config).bind(),
|
|
)
|
|
)
|
|
|
|
for _ in range(10):
|
|
assert h.options(_by_reference=False).remote().result() == "hi"
|
|
|
|
for log_file in os.listdir(logs_dir):
|
|
if not log_file.startswith("replica_default"):
|
|
continue
|
|
|
|
with open(logs_dir / log_file) as f:
|
|
logs = f.read()
|
|
|
|
assert "Exception in callback" not in logs
|
|
assert "PollerCompletionQueue._handle_events" not in logs
|
|
assert "BlockingIOError" not in logs
|
|
assert "Resource temporarily unavailable" not in logs
|
|
|
|
|
|
def check_running():
|
|
assert (
|
|
serve.status().applications[SERVE_DEFAULT_APP_NAME].status
|
|
== ApplicationStatus.RUNNING
|
|
)
|
|
return True
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
sys.platform == "win32", reason="Windows signal handling not compatible"
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"ray_instance",
|
|
[{"RAY_SERVE_PROXY_USE_GRPC": "1"}],
|
|
indirect=True,
|
|
)
|
|
def test_no_spammy_errors_in_grpc_proxy(ray_instance, tmp_dir):
|
|
"""Direct all stdout/stderr to logs, and check that the false errors
|
|
from gRPC are not there in proxy logs."""
|
|
|
|
serve.start(
|
|
http_options={"host": "0.0.0.0"},
|
|
grpc_options={
|
|
"port": 9000,
|
|
"grpc_servicer_functions": [
|
|
"ray.serve.generated.serve_pb2_grpc.add_UserDefinedServiceServicer_to_server", # noqa
|
|
],
|
|
},
|
|
)
|
|
|
|
p = subprocess.Popen(
|
|
[
|
|
"serve",
|
|
"run",
|
|
"--address=auto",
|
|
"ray.serve.tests.test_grpc_e2e.downstream_node",
|
|
],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
)
|
|
# Since we start Serve in a subprocess which is non-blocking, we won't know
|
|
# when `serve run` completes (i.e. done waiting on proxies to be serving), so
|
|
# we query the application until we get a valid response.
|
|
wait_for_condition(
|
|
lambda: requests.post("http://localhost:8000").status_code == 200,
|
|
)
|
|
for _ in range(10):
|
|
assert requests.post("http://localhost:8000").text == "hi"
|
|
|
|
p.send_signal(signal.SIGINT)
|
|
p.wait()
|
|
process_output, _ = p.communicate()
|
|
logs = process_output.decode("utf-8").strip()
|
|
assert "Exception in callback" not in logs
|
|
assert "PollerCompletionQueue._handle_events" not in logs
|
|
assert "BlockingIOError" not in logs
|
|
assert "Resource temporarily unavailable" not in logs
|
|
|
|
|
|
def test_same_loop_handle(serve_instance):
|
|
# With a local handle, where there is no running asyncio loop,
|
|
# setting _run_router_in_separate_loop=False should error.
|
|
h = serve.run(Downstream.bind())
|
|
with pytest.raises(RuntimeError, match="No event loop running"):
|
|
h._init(_run_router_in_separate_loop=False)
|
|
|
|
# However setting _run_router_in_separate_loop=False in a replica
|
|
# should work since there is a running asyncio event loop.
|
|
h = serve.run(
|
|
Ingress.bind(
|
|
Downstream.bind(),
|
|
_run_router_in_separate_loop=False,
|
|
)
|
|
)
|
|
assert h.options(_by_reference=False).remote().result() == "hi"
|
|
|
|
|
|
def test_custom_serialization_method(serve_instance):
|
|
@serve.deployment
|
|
class Downstream:
|
|
def __call__(self, message: str):
|
|
return f"Hello {message}!"
|
|
|
|
h = serve.run(Downstream.bind())
|
|
assert (
|
|
h.options(
|
|
_by_reference=False,
|
|
request_serialization="pickle",
|
|
response_serialization="pickle",
|
|
)
|
|
.remote("world1")
|
|
.result()
|
|
== "Hello world1!"
|
|
)
|
|
|
|
assert (
|
|
h.options(
|
|
_by_reference=False,
|
|
request_serialization="pickle",
|
|
response_serialization="cloudpickle",
|
|
)
|
|
.remote("world2")
|
|
.result()
|
|
== "Hello world2!"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(pytest.main(["-v", "-s", __file__]))
|