2022-03-18 09:12:09 -07:00
|
|
|
import json
|
2022-02-24 08:00:26 -08:00
|
|
|
import logging
|
|
|
|
|
2022-06-14 19:01:51 -07:00
|
|
|
from aiohttp.web import Request, Response
|
|
|
|
|
2022-07-06 19:37:30 -07:00
|
|
|
import dataclasses
|
|
|
|
import ray
|
|
|
|
import aiohttp.web
|
2022-02-24 08:00:26 -08:00
|
|
|
import ray.dashboard.optional_utils as optional_utils
|
2022-06-14 19:01:51 -07:00
|
|
|
import ray.dashboard.utils as dashboard_utils
|
2022-07-06 19:37:30 -07:00
|
|
|
from ray.dashboard.modules.version import (
|
|
|
|
CURRENT_VERSION,
|
|
|
|
VersionResponse,
|
|
|
|
)
|
2022-02-24 08:00:26 -08:00
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
logger.setLevel(logging.INFO)
|
|
|
|
|
|
|
|
routes = optional_utils.ClassMethodRouteTable
|
|
|
|
|
|
|
|
|
2022-04-18 10:25:21 -07:00
|
|
|
# NOTE (shrekris-anyscale): This class uses delayed imports for all
|
2022-07-06 19:37:30 -07:00
|
|
|
# Ray Serve-related modules. That way, users can use the Ray dashboard agent for
|
2022-04-18 10:25:21 -07:00
|
|
|
# non-Serve purposes without downloading Serve dependencies.
|
2022-07-06 19:37:30 -07:00
|
|
|
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,
|
|
|
|
)
|
2022-02-24 08:00:26 -08:00
|
|
|
|
|
|
|
@routes.get("/api/serve/deployments/")
|
2022-07-27 11:21:19 -07:00
|
|
|
@optional_utils.init_ray_and_catch_exceptions()
|
2022-02-24 08:00:26 -08:00
|
|
|
async def get_all_deployments(self, req: Request) -> Response:
|
2022-07-27 11:21:19 -07:00
|
|
|
from ray.serve.schema import ServeApplicationSchema
|
2022-06-14 19:01:51 -07:00
|
|
|
|
2022-07-27 11:21:19 -07:00
|
|
|
client = self.get_serve_client()
|
|
|
|
|
|
|
|
if client is None:
|
|
|
|
config = ServeApplicationSchema.get_empty_schema_dict()
|
|
|
|
else:
|
|
|
|
config = client.get_app_config()
|
2022-03-21 22:14:41 -07:00
|
|
|
|
2022-02-24 08:00:26 -08:00
|
|
|
return Response(
|
2022-07-27 11:21:19 -07:00
|
|
|
text=json.dumps(config),
|
2022-02-25 06:41:07 -08:00
|
|
|
content_type="application/json",
|
|
|
|
)
|
|
|
|
|
|
|
|
@routes.get("/api/serve/deployments/status")
|
2022-07-27 11:21:19 -07:00
|
|
|
@optional_utils.init_ray_and_catch_exceptions()
|
2022-02-25 06:41:07 -08:00
|
|
|
async def get_all_deployment_statuses(self, req: Request) -> Response:
|
2022-07-27 11:21:19 -07:00
|
|
|
from ray.serve.schema import serve_status_to_schema, ServeStatusSchema
|
2022-03-21 22:14:41 -07:00
|
|
|
|
2022-07-27 11:21:19 -07:00
|
|
|
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()
|
2022-05-24 11:07:41 -07:00
|
|
|
|
2022-02-25 06:41:07 -08:00
|
|
|
return Response(
|
2022-07-27 11:21:19 -07:00
|
|
|
text=status_json_str,
|
2022-02-24 08:00:26 -08:00
|
|
|
content_type="application/json",
|
|
|
|
)
|
|
|
|
|
|
|
|
@routes.delete("/api/serve/deployments/")
|
2022-07-27 11:21:19 -07:00
|
|
|
@optional_utils.init_ray_and_catch_exceptions()
|
2022-02-25 06:41:07 -08:00
|
|
|
async def delete_serve_application(self, req: Request) -> Response:
|
2022-03-21 22:14:41 -07:00
|
|
|
from ray import serve
|
|
|
|
|
2022-07-27 11:21:19 -07:00
|
|
|
if self.get_serve_client() is not None:
|
|
|
|
serve.shutdown()
|
|
|
|
|
2022-02-24 08:00:26 -08:00
|
|
|
return Response()
|
|
|
|
|
|
|
|
@routes.put("/api/serve/deployments/")
|
2022-07-27 11:21:19 -07:00
|
|
|
@optional_utils.init_ray_and_catch_exceptions()
|
2022-02-24 08:00:26 -08:00
|
|
|
async def put_all_deployments(self, req: Request) -> Response:
|
2022-06-02 11:06:53 -07:00
|
|
|
from ray.serve.schema import ServeApplicationSchema
|
2022-07-27 11:21:19 -07:00
|
|
|
from ray.serve._private.api import serve_start
|
2022-03-21 22:14:41 -07:00
|
|
|
|
2022-06-02 11:06:53 -07:00
|
|
|
config = ServeApplicationSchema.parse_obj(await req.json())
|
2022-07-27 11:21:19 -07:00
|
|
|
|
|
|
|
client = serve_start(
|
|
|
|
detached=True,
|
2022-07-28 09:26:46 -07:00
|
|
|
http_options={
|
|
|
|
"host": config.host,
|
|
|
|
"port": config.port,
|
|
|
|
"location": "EveryNode",
|
|
|
|
},
|
2022-07-27 11:21:19 -07:00
|
|
|
)
|
2022-07-28 09:26:46 -07:00
|
|
|
|
|
|
|
if client.http_config.host != config.host:
|
|
|
|
return Response(
|
|
|
|
status=400,
|
|
|
|
text=(
|
|
|
|
"Serve is already running on this Ray cluster. Its "
|
|
|
|
f'HTTP host is set to "{client.http_config.host}". '
|
|
|
|
f'However, the requested host is "{config.host}". '
|
|
|
|
f"The requested host must match the running Serve "
|
|
|
|
"application's host. To change the Serve application "
|
|
|
|
"host, shut down Serve on this Ray cluster using the "
|
|
|
|
"`serve shutdown` CLI command or by sending a DELETE "
|
|
|
|
"request to this Ray cluster's "
|
|
|
|
'"/api/serve/deployments/" endpoint. CAUTION: shutting '
|
|
|
|
"down Serve will also shut down all Serve deployments."
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
if client.http_config.port != config.port:
|
|
|
|
return Response(
|
|
|
|
status=400,
|
|
|
|
text=(
|
|
|
|
"Serve is already running on this Ray cluster. Its "
|
|
|
|
f'HTTP port is set to "{client.http_config.port}". '
|
|
|
|
f'However, the requested port is "{config.port}". '
|
|
|
|
f"The requested port must match the running Serve "
|
|
|
|
"application's port. To change the Serve application "
|
|
|
|
"port, shut down Serve on this Ray cluster using the "
|
|
|
|
"`serve shutdown` CLI command or by sending a DELETE "
|
|
|
|
"request to this Ray cluster's "
|
|
|
|
'"/api/serve/deployments/" endpoint. CAUTION: shutting '
|
|
|
|
"down Serve will also shut down all Serve deployments."
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
2022-07-27 11:21:19 -07:00
|
|
|
client.deploy_app(config)
|
2022-02-24 08:00:26 -08:00
|
|
|
|
|
|
|
return Response()
|
|
|
|
|
2022-07-27 11:21:19 -07:00
|
|
|
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
|
|
|
|
|
2022-02-24 08:00:26 -08:00
|
|
|
async def run(self, server):
|
|
|
|
pass
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def is_minimal_module():
|
|
|
|
return False
|