mirror of
https://github.com/vale981/ray
synced 2025-03-07 02:51:39 -05:00

These Serve CLI commands start Serve if it's not already running: * `serve deploy` * `serve config` * `serve status` * `serve shutdown` #27026 introduces the ability to specify a `host` and `port` in the Serve config file. However, once Serve starts running, changing these options requires tearing down the entire Serve application and relaunching it. This limitation is an issue because users can inadvertently start Serve by running one of the `GET`-based CLI commands (i.e. `serve config` or `serve status`) before running `serve deploy`. This change makes `serve deploy` the only CLI command that can start a Serve application on a Ray cluster. The other commands have updated behavior when Serve is not yet running on the cluster. * `serve config`: prints an empty config body. ```yaml import_path: '' runtime_env: {} deployments: [] ``` * `serve status`: prints an empty status body, with a new `app_status` `status` value: `NOT_STARTED`. ```yaml app_status: status: NOT_STARTED message: '' deployment_timestamp: 0 deployment_statuses: [] ``` * `serve shutdown`: performs a no-op.
129 lines
4.2 KiB
Python
129 lines
4.2 KiB
Python
import json
|
|
import logging
|
|
|
|
from aiohttp.web import Request, Response
|
|
|
|
import dataclasses
|
|
import ray
|
|
import aiohttp.web
|
|
import ray.dashboard.optional_utils as optional_utils
|
|
import ray.dashboard.utils as dashboard_utils
|
|
from ray.dashboard.modules.version import (
|
|
CURRENT_VERSION,
|
|
VersionResponse,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logger.setLevel(logging.INFO)
|
|
|
|
routes = optional_utils.ClassMethodRouteTable
|
|
|
|
|
|
# NOTE (shrekris-anyscale): This class uses delayed imports for all
|
|
# Ray Serve-related modules. That way, users can use the Ray dashboard agent for
|
|
# non-Serve purposes without downloading Serve dependencies.
|
|
class ServeAgent(dashboard_utils.DashboardAgentModule):
|
|
def __init__(self, dashboard_agent):
|
|
super().__init__(dashboard_agent)
|
|
|
|
# TODO: It's better to use `/api/version`.
|
|
# It requires a refactor of ClassMethodRouteTable to differentiate the server.
|
|
@routes.get("/api/ray/version")
|
|
async def get_version(self, req: Request) -> Response:
|
|
# NOTE(edoakes): CURRENT_VERSION should be bumped and checked on the
|
|
# client when we have backwards-incompatible changes.
|
|
resp = VersionResponse(
|
|
version=CURRENT_VERSION,
|
|
ray_version=ray.__version__,
|
|
ray_commit=ray.__commit__,
|
|
)
|
|
return Response(
|
|
text=json.dumps(dataclasses.asdict(resp)),
|
|
content_type="application/json",
|
|
status=aiohttp.web.HTTPOk.status_code,
|
|
)
|
|
|
|
@routes.get("/api/serve/deployments/")
|
|
@optional_utils.init_ray_and_catch_exceptions()
|
|
async def get_all_deployments(self, req: Request) -> Response:
|
|
from ray.serve.schema import ServeApplicationSchema
|
|
|
|
client = self.get_serve_client()
|
|
|
|
if client is None:
|
|
config = ServeApplicationSchema.get_empty_schema_dict()
|
|
else:
|
|
config = client.get_app_config()
|
|
|
|
return Response(
|
|
text=json.dumps(config),
|
|
content_type="application/json",
|
|
)
|
|
|
|
@routes.get("/api/serve/deployments/status")
|
|
@optional_utils.init_ray_and_catch_exceptions()
|
|
async def get_all_deployment_statuses(self, req: Request) -> Response:
|
|
from ray.serve.schema import serve_status_to_schema, ServeStatusSchema
|
|
|
|
client = self.get_serve_client()
|
|
|
|
if client is None:
|
|
status_json = ServeStatusSchema.get_empty_schema_dict()
|
|
status_json_str = json.dumps(status_json)
|
|
else:
|
|
status = client.get_serve_status()
|
|
status_json_str = serve_status_to_schema(status).json()
|
|
|
|
return Response(
|
|
text=status_json_str,
|
|
content_type="application/json",
|
|
)
|
|
|
|
@routes.delete("/api/serve/deployments/")
|
|
@optional_utils.init_ray_and_catch_exceptions()
|
|
async def delete_serve_application(self, req: Request) -> Response:
|
|
from ray import serve
|
|
|
|
if self.get_serve_client() is not None:
|
|
serve.shutdown()
|
|
|
|
return Response()
|
|
|
|
@routes.put("/api/serve/deployments/")
|
|
@optional_utils.init_ray_and_catch_exceptions()
|
|
async def put_all_deployments(self, req: Request) -> Response:
|
|
from ray.serve.schema import ServeApplicationSchema
|
|
from ray.serve._private.api import serve_start
|
|
|
|
config = ServeApplicationSchema.parse_obj(await req.json())
|
|
|
|
client = serve_start(
|
|
detached=True,
|
|
http_options={"host": "0.0.0.0", "location": "EveryNode"},
|
|
)
|
|
client.deploy_app(config)
|
|
|
|
return Response()
|
|
|
|
def get_serve_client(self):
|
|
"""Gets the ServeControllerClient to the this cluster's Serve app.
|
|
|
|
return: If Serve is running on this Ray cluster, returns a client to
|
|
the Serve controller. If Serve is not running, returns None.
|
|
"""
|
|
|
|
from ray.serve.context import get_global_client
|
|
from ray.serve.exceptions import RayServeException
|
|
|
|
try:
|
|
return get_global_client(_health_check_controller=True)
|
|
except RayServeException:
|
|
logger.debug("There's no Serve app running on this Ray cluster.")
|
|
return None
|
|
|
|
async def run(self, server):
|
|
pass
|
|
|
|
@staticmethod
|
|
def is_minimal_module():
|
|
return False
|