diff --git a/distributed/deploy/local.py b/distributed/deploy/local.py index e090f6649e..725440ec0e 100644 --- a/distributed/deploy/local.py +++ b/distributed/deploy/local.py @@ -233,7 +233,6 @@ def __init__( port=scheduler_port, interface=interface, protocol=protocol, - dashboard=dashboard_address is not None, dashboard_address=dashboard_address, blocked_handlers=blocked_handlers, ), diff --git a/distributed/http/scheduler/tests/test_missing_bokeh.py b/distributed/http/scheduler/tests/test_missing_bokeh.py index c29c14c395..04f29f6fdf 100644 --- a/distributed/http/scheduler/tests/test_missing_bokeh.py +++ b/distributed/http/scheduler/tests/test_missing_bokeh.py @@ -8,67 +8,104 @@ from tornado.httpclient import AsyncHTTPClient from distributed import Scheduler -from distributed.utils_test import gen_test +from distributed.utils_test import captured_logger, gen_test @gen_test() async def test_missing_bokeh(): - with mock.patch.dict(sys.modules, {"bokeh": None}): - # NOTE: mocking here is fiddly; if any of these get refactored or import order - # changes, this may break. - sys.modules.pop("distributed.dashboard.scheduler", None) - sys.modules.pop("distributed.dashboard.core", None) - sys.modules.pop("distributed.dashboard.utils", None) - - async with Scheduler(dashboard_address=":0") as s: - http_client = AsyncHTTPClient() - response = await http_client.fetch( - f"http://localhost:{s.http_server.port}/status" - ) - assert response.code == 200 - body = response.body.decode() - assert "Dask needs bokeh" in body + with captured_logger("distributed.scheduler") as log: + with mock.patch.dict(sys.modules, {"bokeh": None}): + # NOTE: mocking here is fiddly; if any of these get refactored or import order + # changes, this may break. + sys.modules.pop("distributed.dashboard.scheduler", None) + sys.modules.pop("distributed.dashboard.core", None) + sys.modules.pop("distributed.dashboard.utils", None) + + async with Scheduler(dashboard=True, dashboard_address=":0") as s: + http_client = AsyncHTTPClient() + response = await http_client.fetch( + f"http://localhost:{s.http_server.port}/status" + ) + assert response.code == 200 + body = response.body.decode() + assert "Dask needs bokeh" in body + + assert "Dashboard is disabled" in log.getvalue() + + +@gen_test() +async def test_implicit_dashboard_request_stays_silent(): + with captured_logger("distributed.scheduler") as log: + with mock.patch.dict(sys.modules, {"bokeh": None}): + sys.modules.pop("distributed.dashboard.scheduler", None) + sys.modules.pop("distributed.dashboard.core", None) + sys.modules.pop("distributed.dashboard.utils", None) + + async with Scheduler(dashboard_address=":0") as s: + http_client = AsyncHTTPClient() + response = await http_client.fetch( + f"http://localhost:{s.http_server.port}/status" + ) + assert response.code == 200 + body = response.body.decode() + assert "Dask needs bokeh" in body + + assert "Dashboard is disabled" not in log.getvalue() @gen_test() async def test_bokeh_version_too_low(): pytest.importorskip("bokeh") - with mock.patch.dict(sys.modules): - # Remove these imports, so when the scheduler imports - # `distributed.dashboard.scheduler`, it has to re-import `dashboard.core`, which - # is where the bokeh version detection happens at import time, via - # `dashboard.utils.BOKEH_VERSION`. - sys.modules.pop("distributed.dashboard.scheduler", None) - sys.modules.pop("distributed.dashboard.core", None) - - with mock.patch("distributed.dashboard.utils.BOKEH_VERSION", Version("1.4.0")): - with pytest.warns(UserWarning, match="1.4.0"): - async with Scheduler(dashboard_address=":0") as s: - http_client = AsyncHTTPClient() - response = await http_client.fetch( - f"http://localhost:{s.http_server.port}/status" - ) - assert response.code == 200 - body = response.body.decode() - assert "Dask needs bokeh" in body + with captured_logger("distributed.scheduler") as log: + with mock.patch.dict(sys.modules): + # Remove these imports, so when the scheduler imports + # `distributed.dashboard.scheduler`, it has to re-import `dashboard.core`, which + # is where the bokeh version detection happens at import time, via + # `dashboard.utils.BOKEH_VERSION`. + sys.modules.pop("distributed.dashboard.scheduler", None) + sys.modules.pop("distributed.dashboard.core", None) + + with mock.patch( + "distributed.dashboard.utils.BOKEH_VERSION", Version("1.4.0") + ): + with pytest.warns(UserWarning, match="1.4.0"): + async with Scheduler(dashboard=True, dashboard_address=":0") as s: + http_client = AsyncHTTPClient() + response = await http_client.fetch( + f"http://localhost:{s.http_server.port}/status" + ) + assert response.code == 200 + body = response.body.decode() + assert "Dask needs bokeh" in body + + assert "Dashboard is disabled" in log.getvalue() + assert "bokeh>=3.1.0" in log.getvalue() + assert "bokeh=1.4.0" in log.getvalue() @gen_test() async def test_bokeh_version_too_high(): pytest.importorskip("bokeh") - with mock.patch.dict(sys.modules): - sys.modules.pop("distributed.dashboard.scheduler", None) - sys.modules.pop("distributed.dashboard.core", None) - - with mock.patch("distributed.dashboard.utils.BOKEH_VERSION", Version("3.0.1")): - with pytest.warns(UserWarning, match="3.0.1"): - async with Scheduler(dashboard_address=":0") as s: - http_client = AsyncHTTPClient() - response = await http_client.fetch( - f"http://localhost:{s.http_server.port}/status" - ) - assert response.code == 200 - body = response.body.decode() - assert "Dask needs bokeh" in body + with captured_logger("distributed.scheduler") as log: + with mock.patch.dict(sys.modules): + sys.modules.pop("distributed.dashboard.scheduler", None) + sys.modules.pop("distributed.dashboard.core", None) + + with mock.patch( + "distributed.dashboard.utils.BOKEH_VERSION", Version("3.0.1") + ): + with pytest.warns(UserWarning, match="3.0.1"): + async with Scheduler(dashboard=True, dashboard_address=":0") as s: + http_client = AsyncHTTPClient() + response = await http_client.fetch( + f"http://localhost:{s.http_server.port}/status" + ) + assert response.code == 200 + body = response.body.decode() + assert "Dask needs bokeh" in body + + assert "Dashboard is disabled" in log.getvalue() + assert "bokeh>=3.1.0" in log.getvalue() + assert "bokeh=3.0.1" in log.getvalue() diff --git a/distributed/scheduler.py b/distributed/scheduler.py index 445ffd96e8..9d38423315 100644 --- a/distributed/scheduler.py +++ b/distributed/scheduler.py @@ -3992,14 +3992,22 @@ def __init__( ) http_server_modules = dask.config.get("distributed.scheduler.http.routes") + explicit_dashboard_request = dashboard is True show_dashboard = dashboard or (dashboard is None and dashboard_address) # install vanilla route if show_dashboard but bokeh is not installed if show_dashboard: try: import distributed.dashboard.scheduler - except ImportError: + except ImportError as e: show_dashboard = False http_server_modules.append("distributed.http.scheduler.missing_bokeh") + if explicit_dashboard_request: + logger.warning( + "Dashboard is disabled; the scheduler will serve a diagnostic " + "placeholder page instead. Explicit dashboard request could not " + "be satisfied because %s", + e, + ) routes = get_handlers( server=self, modules=http_server_modules, prefix=http_prefix )