Files
ray-project--ray/python/ray/tests/test_dashboard.py
T
2026-07-13 13:17:40 +08:00

211 lines
5.4 KiB
Python

import re
import subprocess
import sys
import time
import pytest
import requests
import ray
from ray._common.test_utils import (
run_string_as_driver,
wait_for_condition,
)
from ray._private import ray_constants
import psutil
def search_agents(cluster):
all_processes = cluster.head_node.all_processes
raylet_proc_info = all_processes[ray_constants.PROCESS_TYPE_RAYLET][0]
raylet_proc = psutil.Process(raylet_proc_info.process.pid)
def _search_agent(processes):
for p in processes:
try:
for c in p.cmdline():
if ray_constants.AGENT_PROCESS_TYPE_DASHBOARD_AGENT[:15] in c:
return p
except Exception:
pass
agent_proc = _search_agent(raylet_proc.children())
return agent_proc
def test_ray_start_default_port_conflict(call_ray_stop_only, shutdown_only):
subprocess.check_call(["ray", "start", "--head"])
ray.init(address="auto")
assert (
str(ray_constants.DEFAULT_DASHBOARD_PORT)
in ray._private.worker.get_dashboard_url()
)
error_raised = False
try:
subprocess.check_output(
[
"ray",
"start",
"--head",
"--port",
"9999", # use a different gcs port
"--include-dashboard=True",
],
stderr=subprocess.PIPE,
)
except subprocess.CalledProcessError as e:
assert b"already occupied" in e.stderr
error_raised = True
assert error_raised, "ray start should cause a conflict error"
def test_port_auto_increment(shutdown_only):
ray.init()
url = ray._private.worker.get_dashboard_url()
def dashboard_available():
try:
requests.get("http://" + url).status_code == 200
return True
except Exception:
return False
wait_for_condition(dashboard_available)
run_string_as_driver(
f"""
import ray
from ray._common.test_utils import wait_for_condition
import requests
ray.init()
url = ray._private.worker.get_dashboard_url()
assert url != "{url}"
def dashboard_available():
try:
requests.get("http://"+url).status_code == 200
return True
except:
return False
wait_for_condition(dashboard_available)
ray.shutdown()
"""
)
@pytest.mark.parametrize(
"listen_port",
[9999],
indirect=True,
)
def test_port_conflict(listen_port, call_ray_stop_only, shutdown_only):
try:
subprocess.check_output(
[
"ray",
"start",
"--head",
"--port",
"9989",
"--dashboard-port",
"9999",
"--include-dashboard=True",
],
stderr=subprocess.PIPE,
)
except subprocess.CalledProcessError as e:
assert b"already occupied" in e.stderr
with pytest.raises(ValueError, match="already occupied"):
ray.init(dashboard_port=9999, include_dashboard=True)
def test_dashboard(shutdown_only):
addresses = ray.init(include_dashboard=True, num_cpus=1)
dashboard_url = addresses["webui_url"]
assert ray._private.worker.get_dashboard_url() == dashboard_url
assert re.match(r"^(localhost|\d+\.\d+\.\d+\.\d+):\d+$", dashboard_url)
start_time = time.time()
while True:
try:
node_info_url = f"http://{dashboard_url}/nodes"
resp = requests.get(node_info_url, params={"view": "summary"})
resp.raise_for_status()
summaries = resp.json()
assert summaries["result"] is True
assert "msg" in summaries
break
except (requests.exceptions.ConnectionError, AssertionError):
if time.time() > start_time + 30:
out_log = None
with open(
"{}/logs/dashboard.log".format(addresses["session_dir"]), "r"
) as f:
out_log = f.read()
raise Exception(
"Timed out while waiting for dashboard to start. "
f"Dashboard output log: {out_log}\n"
)
conflict_port = 34567
configured_test_port = 34568
def run_tasks_without_runtime_env():
assert ray.is_initialized()
@ray.remote
def f():
pass
for _ in range(10):
time.sleep(1)
ray.get(f.remote())
def run_tasks_with_runtime_env():
assert ray.is_initialized()
@ray.remote(runtime_env={"pip": ["pip-install-test==0.5"]})
def f():
import pip_install_test # noqa
pass
for _ in range(3):
time.sleep(1)
ray.get(f.remote())
@pytest.mark.skipif(
sys.platform == "win32", reason="`runtime_env` with `pip` not supported on Windows."
)
@pytest.mark.parametrize(
"listen_port",
[conflict_port],
indirect=True,
)
@pytest.mark.parametrize(
"call_ray_start",
[
f"ray start --head --num-cpus=1 --metrics-export-port={conflict_port}",
f"ray start --head --num-cpus=1 --dashboard-agent-listen-port={conflict_port}",
],
indirect=True,
)
def test_dashboard_agent_metrics_or_http_port_conflict(listen_port, call_ray_start):
address = call_ray_start
ray.init(address=address)
# Tasks with runtime env still work when other agent port conflicts,
# except grpc port.
run_tasks_with_runtime_env()
if __name__ == "__main__":
sys.exit(pytest.main(["-sv", __file__]))