From 0c50c0754e6c329a9733d6f35b183c087b34bf04 Mon Sep 17 00:00:00 2001 From: Nizamudeen A Date: Tue, 1 Apr 2025 12:48:23 +0530 Subject: [PATCH] mgr/dashboard: introduce dashboard setting to resolve rgw hostname drops using the `rgw_dns_name` to resolve the rgw hosts in dashboard and introduces a command `ceph dashboard set-rgw-hostname ` to set custom dns names for each gateways. Once set dashboard will pick up that address for the selected gateway. And a config can be unset by `ceph dashboard unset-rgw-hostname ` Fixes: https://tracker.ceph.com/issues/70744 Signed-off-by: Nizamudeen A --- doc/mgr/dashboard.rst | 13 +++++ src/pybind/mgr/dashboard/module.py | 18 +++++++ .../mgr/dashboard/services/rgw_client.py | 15 ++++-- src/pybind/mgr/dashboard/services/service.py | 17 ++++++ src/pybind/mgr/dashboard/settings.py | 1 + .../mgr/dashboard/tests/test_rgw_client.py | 54 ++++++++++++++++++- 6 files changed, 112 insertions(+), 6 deletions(-) diff --git a/doc/mgr/dashboard.rst b/doc/mgr/dashboard.rst index 32824fab4b5..76e815cdb46 100644 --- a/doc/mgr/dashboard.rst +++ b/doc/mgr/dashboard.rst @@ -429,6 +429,19 @@ the host name: ceph dashboard set-rgw-api-ssl-verify False +To set a custom hostname or address for an RGW gateway, set the value of ``RGW_HOSTNAME_PER_DAEMON`` +accordingly: + +.. promt:: bash $ + + ceph dashboard set-rgw-hostname + +The setting can be unset using: + +.. promt:: bash $ + + ceph dashboard unset-rgw-hostname + If the Object Gateway takes too long to process requests and the dashboard runs into timeouts, you can set the timeout value to your needs: diff --git a/src/pybind/mgr/dashboard/module.py b/src/pybind/mgr/dashboard/module.py index 846401f76fc..295403f6fe3 100644 --- a/src/pybind/mgr/dashboard/module.py +++ b/src/pybind/mgr/dashboard/module.py @@ -429,6 +429,24 @@ class Module(MgrModule, CherryPyConfig): return 0, 'RGW credentials configured', '' + @CLIWriteCommand("dashboard set-rgw-hostname") + def set_rgw_hostname(self, daemon_name: str, hostname: str): + try: + rgw_service_manager = RgwServiceManager() + rgw_service_manager.set_rgw_hostname(daemon_name, hostname) + return 0, f'RGW hostname for daemon {daemon_name} configured', '' + except Exception as error: + return -errno.EINVAL, '', str(error) + + @CLIWriteCommand("dashboard unset-rgw-hostname") + def unset_rgw_hostname(self, daemon_name: str): + try: + rgw_service_manager = RgwServiceManager() + rgw_service_manager.unset_rgw_hostname(daemon_name) + return 0, f'RGW hostname for daemon {daemon_name} resetted', '' + except Exception as error: + return -errno.EINVAL, '', str(error) + @CLIWriteCommand("dashboard set-login-banner") def set_login_banner(self, inbuf: str): ''' diff --git a/src/pybind/mgr/dashboard/services/rgw_client.py b/src/pybind/mgr/dashboard/services/rgw_client.py index ce33e37a18f..fb633726805 100755 --- a/src/pybind/mgr/dashboard/services/rgw_client.py +++ b/src/pybind/mgr/dashboard/services/rgw_client.py @@ -22,7 +22,7 @@ try: except ModuleNotFoundError: logging.error("Module 'xmltodict' is not installed.") -from mgr_util import build_url, name_to_config_section +from mgr_util import build_url from .. import mgr from ..awsauth import S3Auth @@ -100,9 +100,12 @@ def _determine_rgw_addr(daemon_info: Dict[str, Any]) -> RgwDaemon: Parse RGW daemon info to determine the configured host (IP address) and port. """ daemon = RgwDaemon() - rgw_dns_name = CephService.send_command('mon', 'config get', - who=name_to_config_section('rgw.' + daemon_info['metadata']['id']), # noqa E501 #pylint: disable=line-too-long - key='rgw_dns_name').rstrip() + rgw_dns_name = '' + if ( + Settings.RGW_HOSTNAME_PER_DAEMON + and daemon_info['metadata']['id'] in Settings.RGW_HOSTNAME_PER_DAEMON + ): + rgw_dns_name = Settings.RGW_HOSTNAME_PER_DAEMON[daemon_info['metadata']['id']] daemon.port, daemon.ssl = _parse_frontend_config(daemon_info['metadata']['frontend_config#0']) @@ -279,7 +282,9 @@ class RgwClient(RestClient): return (Settings.RGW_API_ACCESS_KEY, Settings.RGW_API_SECRET_KEY, Settings.RGW_API_ADMIN_RESOURCE, - Settings.RGW_API_SSL_VERIFY) + Settings.RGW_API_SSL_VERIFY, + Settings.RGW_HOSTNAME_PER_DAEMON + ) @staticmethod def instance(userid: Optional[str] = None, diff --git a/src/pybind/mgr/dashboard/services/service.py b/src/pybind/mgr/dashboard/services/service.py index 679e2919b7d..057b6f03ef7 100644 --- a/src/pybind/mgr/dashboard/services/service.py +++ b/src/pybind/mgr/dashboard/services/service.py @@ -204,3 +204,20 @@ class RgwServiceManager: except (AssertionError, SubprocessError) as error: logger.exception(error) raise NoCredentialsException + + def set_rgw_hostname(self, daemon_name: str, hostname: str): + if not Settings.RGW_HOSTNAME_PER_DAEMON: + Settings.RGW_HOSTNAME_PER_DAEMON = {daemon_name: hostname} + return + + rgw_hostname_setting = Settings.RGW_HOSTNAME_PER_DAEMON + rgw_hostname_setting[daemon_name] = hostname + Settings.RGW_HOSTNAME_PER_DAEMON = rgw_hostname_setting + + def unset_rgw_hostname(self, daemon_name: str): + if not Settings.RGW_HOSTNAME_PER_DAEMON: + return + + rgw_hostname_setting = Settings.RGW_HOSTNAME_PER_DAEMON + rgw_hostname_setting.pop(daemon_name, None) + Settings.RGW_HOSTNAME_PER_DAEMON = rgw_hostname_setting diff --git a/src/pybind/mgr/dashboard/settings.py b/src/pybind/mgr/dashboard/settings.py index e98383070e2..9cb1e3de289 100644 --- a/src/pybind/mgr/dashboard/settings.py +++ b/src/pybind/mgr/dashboard/settings.py @@ -67,6 +67,7 @@ class Options(object): RGW_API_SECRET_KEY = Setting('', [dict, str]) RGW_API_ADMIN_RESOURCE = Setting('admin', [str]) RGW_API_SSL_VERIFY = Setting(True, [bool]) + RGW_HOSTNAME_PER_DAEMON = Setting('', [dict, str]) # Ceph Issue Tracker API Access Key ISSUE_TRACKER_API_KEY = Setting('', [str]) diff --git a/src/pybind/mgr/dashboard/tests/test_rgw_client.py b/src/pybind/mgr/dashboard/tests/test_rgw_client.py index f2d34ca5458..898803dd29a 100644 --- a/src/pybind/mgr/dashboard/tests/test_rgw_client.py +++ b/src/pybind/mgr/dashboard/tests/test_rgw_client.py @@ -6,7 +6,8 @@ from unittest.mock import Mock, patch from .. import mgr from ..exceptions import DashboardException -from ..services.rgw_client import NoRgwDaemonsException, RgwClient, _parse_frontend_config +from ..services.rgw_client import NoRgwDaemonsException, RgwClient, \ + _determine_rgw_addr, _parse_frontend_config from ..services.service import NoCredentialsException from ..settings import Settings from ..tests import CLICommandTestMixin, RgwStub @@ -273,6 +274,57 @@ class RgwClientTest(TestCase, CLICommandTestMixin): retention_period_years=years )) + def test_set_rgw_hostname(self): + result = self.exec_cmd( + 'set-rgw-hostname', + daemon_name='test_daemon', + hostname='example.hostname.com' + ) + self.assertEqual( + result, + 'RGW hostname for daemon test_daemon configured' + ) + self.assertEqual( + Settings.RGW_HOSTNAME_PER_DAEMON, + {'test_daemon': 'example.hostname.com'} + ) + + @patch("dashboard.services.rgw_client.RgwDaemon") + def test_hostname_when_rgw_hostname_config_is_set(self, mock_daemons): + mock_instance = Mock() + mock_daemons.return_value = mock_instance + + self.test_set_rgw_hostname() + + daemon_info = { + 'metadata': { + 'id': 'test_daemon', + 'hostname': 'my-hostname.com', + 'frontend_config#0': 'beast port=8000' + }, + 'addr': '192.0.2.1' + } + + result = _determine_rgw_addr(daemon_info) + self.assertEqual(result.host, "example.hostname.com") + + @patch("dashboard.services.rgw_client.RgwDaemon") + def test_hostname_when_rgw_hostname_config_is_not_set(self, mock_daemons): + mock_instance = Mock() + mock_daemons.return_value = mock_instance + + daemon_info = { + 'metadata': { + 'id': 'test_daemon', + 'hostname': 'my.hostname.com', + 'frontend_config#0': 'beast port=8000' + }, + 'addr': '192.168.178.3:49774/1534999298' + } + + result = _determine_rgw_addr(daemon_info) + self.assertEqual(result.host, "192.168.178.3") + class RgwClientHelperTest(TestCase): def test_parse_frontend_config_1(self): -- 2.39.5