ray/dashboard/modules/log/log_head.py
SangBin Cho e62c0052a0
[Dashboard] Agent in minimal ray installation (#21817)
This is the second part of https://docs.google.com/document/d/12qP3x5uaqZSKS-A_kK0ylPOp0E02_l-deAbmm8YtdFw/edit#. After this PR, dashboard agents will fully work with minimal ray installation.

Note that this PR requires to introduce "aioredis", "frozenlist", and "aiosignal" to the minimal installation. These dependencies are very small (or will be removed soon), and including them to minimal makes thing very easy. Please see the below for the reasoning.
2022-01-26 04:03:54 -08:00

100 lines
3.6 KiB
Python

import logging
import aiohttp.web
import ray.dashboard.modules.log.log_utils as log_utils
import ray.dashboard.utils as dashboard_utils
import ray.dashboard.optional_utils as dashboard_optional_utils
from ray.dashboard.datacenter import DataSource, GlobalSignals
logger = logging.getLogger(__name__)
routes = dashboard_optional_utils.ClassMethodRouteTable
class LogHead(dashboard_utils.DashboardHeadModule):
LOG_URL_TEMPLATE = "http://{ip}:{port}/logs"
def __init__(self, dashboard_head):
super().__init__(dashboard_head)
# We disable auto_decompress when forward / proxy log url.
self._proxy_session = aiohttp.ClientSession(auto_decompress=False)
log_utils.register_mimetypes()
routes.static("/logs", self._dashboard_head.log_dir, show_index=True)
GlobalSignals.node_info_fetched.append(
self.insert_log_url_to_node_info)
GlobalSignals.node_summary_fetched.append(
self.insert_log_url_to_node_info)
async def insert_log_url_to_node_info(self, node_info):
node_id = node_info.get("raylet", {}).get("nodeId")
if node_id is None:
return
agent_port = DataSource.agents.get(node_id)
if agent_port is None:
return
agent_http_port, _ = agent_port
log_url = self.LOG_URL_TEMPLATE.format(
ip=node_info.get("ip"), port=agent_http_port)
node_info["logUrl"] = log_url
@routes.get("/log_index")
async def get_log_index(self, req) -> aiohttp.web.Response:
url_list = []
agent_ips = []
for node_id, ports in DataSource.agents.items():
ip = DataSource.node_id_to_ip[node_id]
agent_ips.append(ip)
url_list.append(
self.LOG_URL_TEMPLATE.format(ip=ip, port=str(ports[0])))
if self._dashboard_head.ip not in agent_ips:
url_list.append(
self.LOG_URL_TEMPLATE.format(
ip=self._dashboard_head.ip,
port=self._dashboard_head.http_port))
return aiohttp.web.Response(
text=self._directory_as_html(url_list), content_type="text/html")
@routes.get("/log_proxy")
async def get_log_from_proxy(self, req) -> aiohttp.web.StreamResponse:
url = req.query.get("url")
if not url:
raise Exception("url is None.")
body = await req.read()
async with self._proxy_session.request(
req.method, url, data=body, headers=req.headers) as r:
sr = aiohttp.web.StreamResponse(
status=r.status, reason=r.reason, headers=req.headers)
sr.content_length = r.content_length
sr.content_type = r.content_type
sr.charset = r.charset
writer = await sr.prepare(req)
async for data in r.content.iter_any():
await writer.write(data)
return sr
@staticmethod
def _directory_as_html(url_list) -> str:
# returns directory's index as html
index_of = "Index of logs"
h1 = f"<h1>{index_of}</h1>"
index_list = []
for url in sorted(url_list):
index_list.append(f'<li><a href="{url}">{url}</a></li>')
index_list = "\n".join(index_list)
ul = f"<ul>\n{index_list}\n</ul>"
body = f"<body>\n{h1}\n{ul}\n</body>"
head_str = f"<head>\n<title>{index_of}</title>\n</head>"
html = f"<html>\n{head_str}\n{body}\n</html>"
return html
async def run(self, server):
pass
@staticmethod
def is_minimal_module():
return False