From f45a1bfc93596f17370ee38ce241ae8123d81e8e Mon Sep 17 00:00:00 2001 From: Afreen Misbah Date: Tue, 23 Jun 2026 19:00:22 +0530 Subject: [PATCH] mgr/dashboard: fix bind address regression from CherryPy isolation The CherryPy isolation refactor (PR #67227) accidentally changed the dashboard bind address from wildcard (*:8443) to mon_ip:8443. The get_mgr_ip() replacement was originally only for URI generation, but the refactor passed the mutated address to CherryPyMgr.mount() as the actual socket bind address. This breaks the management gateway when its VIP is not on the same interface as mon_ip, as the dashboard becomes unreachable on other interfaces. Preserve the original wildcard address for binding and only use get_mgr_ip() for the advertised URI. Add regression test to prevent future confusion between bind_addr and server_addr. Fixes: https://tracker.ceph.com/issues/77491 Signed-off-by: Afreen Misbah --- src/pybind/mgr/dashboard/module.py | 3 +- .../mgr/dashboard/tests/test_settings.py | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/pybind/mgr/dashboard/module.py b/src/pybind/mgr/dashboard/module.py index fffa768575b..7b7bb3b77de 100644 --- a/src/pybind/mgr/dashboard/module.py +++ b/src/pybind/mgr/dashboard/module.py @@ -209,6 +209,7 @@ class CherryPyConfig(object): self._url_prefix = prepare_url_prefix(self.get_module_option( # type: ignore 'url_prefix', default='')) + bind_addr = server_addr if server_addr in ['::', '0.0.0.0']: server_addr = self.get_mgr_ip() # type: ignore base_url = build_url( @@ -217,7 +218,7 @@ class CherryPyConfig(object): port=server_port, ) uri = f'{base_url}{self.url_prefix}/' - return uri, (server_addr, server_port), ssl_info, config + return uri, (bind_addr, server_port), ssl_info, config def await_configuration(self): """ diff --git a/src/pybind/mgr/dashboard/tests/test_settings.py b/src/pybind/mgr/dashboard/tests/test_settings.py index d6c94b6b0b8..17dad550400 100644 --- a/src/pybind/mgr/dashboard/tests/test_settings.py +++ b/src/pybind/mgr/dashboard/tests/test_settings.py @@ -2,11 +2,13 @@ import errno import unittest +from unittest.mock import MagicMock from mgr_module import ERROR_MSG_EMPTY_INPUT_FILE from .. import settings from ..controllers.settings import Settings as SettingsController +from ..module import CherryPyConfig from ..settings import Settings, handle_option_command from ..tests import ControllerTestCase, KVStoreMockMixin @@ -125,6 +127,33 @@ class SettingsTest(unittest.TestCase, KVStoreMockMixin): self.assertEqual(str(ctx.exception), "type object 'Options' has no attribute 'NON_EXISTENT_OPTION'") + def _setup_cherrypy_config(self, server_addr, server_port): + obj = CherryPyConfig() + config_map = { + 'server_addr': server_addr, + 'ssl': False, + 'server_port': server_port + } + obj.get_localized_module_option = MagicMock( + side_effect=lambda key, default=None: config_map.get(key, default)) + obj.get_mgr_ip = MagicMock(return_value='192.168.1.10') + obj.get_module_option = MagicMock(return_value='') + obj.get_mgr_id = MagicMock(return_value='test') + obj.module_name = 'dashboard' + obj.log = MagicMock() + obj.update_cherrypy_config = MagicMock() + return obj + + def test_wildcard_bind_addr_preserved(self): + obj = self._setup_cherrypy_config('::', 8080) + _, bind_addr, _, _ = obj._configure() # pylint: disable=protected-access + self.assertEqual(bind_addr, ('::', 8080)) + + def test_wildcard_bind_addr_preserved_ipv4(self): + obj = self._setup_cherrypy_config('0.0.0.0', 8443) + _, bind_addr, _, _ = obj._configure() # pylint: disable=protected-access + self.assertEqual(bind_addr, ('0.0.0.0', 8443)) + class SettingsControllerTest(ControllerTestCase, KVStoreMockMixin): @classmethod -- 2.47.3