From bd0a36c1d6cdd27d12e19af74d8a55eda3f21b45 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Alfonso=20Mart=C3=ADnez?= Date: Fri, 6 Aug 2021 08:57:47 +0200 Subject: [PATCH] mgr/dashboard: connect-rgw: rename to set-rgw-credentials; refactoring MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit - Rename the dashboard command to better reflect its behavior. - Rename '_radosgw_admin' method to 'send_rgwadmin_command' for consistency with 'send_mon_command' and move it to the mgr_module.py . - Cleanup: remove unneeded rgw settings. - Better error handling and test coverage. Fixes: https://tracker.ceph.com/issues/44605 Signed-off-by: Alfonso Martínez (cherry picked from commit 6e20ef1dd35f3681d14cd4e08ca63eb20edc2c88) --- doc/mgr/dashboard.rst | 10 +- qa/tasks/mgr/dashboard/test_rgw.py | 14 +- qa/tasks/mgr/dashboard/test_settings.py | 4 +- src/pybind/mgr/cephadm/serve.py | 2 +- .../cluster/mgr-modules.e2e-spec.ts | 5 - src/pybind/mgr/dashboard/module.py | 5 +- .../mgr/dashboard/services/rgw_client.py | 151 +++++++---------- src/pybind/mgr/dashboard/settings.py | 4 - src/pybind/mgr/dashboard/tests/__init__.py | 2 - .../mgr/dashboard/tests/test_rgw_client.py | 156 ++++++++++++------ src/pybind/mgr/mgr_module.py | 31 ++++ 11 files changed, 214 insertions(+), 170 deletions(-) diff --git a/doc/mgr/dashboard.rst b/doc/mgr/dashboard.rst index 54a139a511468..3ac0e0333b0a9 100644 --- a/doc/mgr/dashboard.rst +++ b/doc/mgr/dashboard.rst @@ -377,14 +377,18 @@ Enabling the Object Gateway Management Frontend ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When RGW is deployed with cephadm, the RGW credentials used by the -dashboard will be automatically created. You can also manually force the -credentials to be created with:: +dashboard will be automatically configured. You can also manually force the +credentials to be set up with:: - $ ceph dashboard connect-rgw + $ ceph dashboard set-rgw-credentials This will create an RGW user with uid ``dashboard`` for each realm in the system. +If you've configured a custom 'admin' resource in your RGW admin API, you should set it here also:: + + $ ceph dashboard set-rgw-api-admin-resource + If you are using a self-signed certificate in your Object Gateway setup, you should disable certificate verification in the dashboard to avoid refused connections, e.g. caused by certificates signed by unknown CA or not matching diff --git a/qa/tasks/mgr/dashboard/test_rgw.py b/qa/tasks/mgr/dashboard/test_rgw.py index 525948a855c65..b1a53a72140f0 100644 --- a/qa/tasks/mgr/dashboard/test_rgw.py +++ b/qa/tasks/mgr/dashboard/test_rgw.py @@ -77,22 +77,22 @@ class RgwApiCredentialsTest(RgwTestCase): self.logout() self._ceph_cmd(['mgr', 'module', 'disable', 'dashboard']) self._ceph_cmd(['mgr', 'module', 'enable', 'dashboard', '--force']) - # Set the default credentials. - self._ceph_cmd_with_secret(['dashboard', 'set-rgw-api-secret-key'], 'admin') - self._ceph_cmd_with_secret(['dashboard', 'set-rgw-api-access-key'], 'admin') super(RgwApiCredentialsTest, self).setUp() - def test_no_access_secret_key(self): - self._ceph_cmd(['dashboard', 'reset-rgw-api-secret-key']) - self._ceph_cmd(['dashboard', 'reset-rgw-api-access-key']) + def test_invalid_credentials(self): + self._ceph_cmd_with_secret(['dashboard', 'set-rgw-api-secret-key'], 'invalid') + self._ceph_cmd_with_secret(['dashboard', 'set-rgw-api-access-key'], 'invalid') resp = self._get('/api/rgw/user') self.assertStatus(500) self.assertIn('detail', resp) self.assertIn('component', resp) - self.assertIn('No RGW credentials found', resp['detail']) + self.assertIn('Error connecting to Object Gateway', resp['detail']) self.assertEqual(resp['component'], 'rgw') def test_success(self): + # Set the default credentials. + self._ceph_cmd_with_secret(['dashboard', 'set-rgw-api-secret-key'], 'admin') + self._ceph_cmd_with_secret(['dashboard', 'set-rgw-api-access-key'], 'admin') data = self._get('/api/rgw/status') self.assertStatus(200) self.assertIn('available', data) diff --git a/qa/tasks/mgr/dashboard/test_settings.py b/qa/tasks/mgr/dashboard/test_settings.py index 46750292e1c94..869ea2ca445fb 100644 --- a/qa/tasks/mgr/dashboard/test_settings.py +++ b/qa/tasks/mgr/dashboard/test_settings.py @@ -51,8 +51,8 @@ class SettingsTest(DashboardTestCase): def test_bulk_set(self): self._put('/api/settings', { - 'RGW_API_HOST': 'somehost', - 'RGW_API_PORT': 7777, + 'USER_PWD_EXPIRATION_WARNING_1': 12, + 'USER_PWD_EXPIRATION_WARNING_2': 6, }) self.assertStatus(200) diff --git a/src/pybind/mgr/cephadm/serve.py b/src/pybind/mgr/cephadm/serve.py index 1c9bd5f4c56e5..071dd4cbef3c2 100644 --- a/src/pybind/mgr/cephadm/serve.py +++ b/src/pybind/mgr/cephadm/serve.py @@ -85,7 +85,7 @@ class CephadmServe: self.mgr.need_connect_dashboard_rgw = False if 'dashboard' in self.mgr.get('mgr_map')['modules']: self.log.info('Checking dashboard <-> RGW credentials') - self.mgr.remote('dashboard', 'connect_rgw') + self.mgr.remote('dashboard', 'set_rgw_credentials') if not self.mgr.paused: self.mgr.to_remove_osds.process_removal_queue() diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.e2e-spec.ts index 82c6878969935..50656feceb76b 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.e2e-spec.ts @@ -29,11 +29,6 @@ describe('Manager modules page', () => { it('should test editing on dashboard module', () => { const dashboardArr: Input[] = [ - { - id: 'RGW_API_USER_ID', - newValue: 'rq', - oldValue: '' - }, { id: 'GRAFANA_API_PASSWORD', newValue: 'rafa', diff --git a/src/pybind/mgr/dashboard/module.py b/src/pybind/mgr/dashboard/module.py index 287aaf48502fd..6abacebcfb352 100644 --- a/src/pybind/mgr/dashboard/module.py +++ b/src/pybind/mgr/dashboard/module.py @@ -401,12 +401,11 @@ class Module(MgrModule, CherryPyConfig): return result return 0, 'Self-signed certificate created', '' - @CLIWriteCommand("dashboard connect-rgw") - def connect_rgw(self): + @CLIWriteCommand("dashboard set-rgw-credentials") + def set_rgw_credentials(self): try: configure_rgw_credentials() except Exception as error: - logger.exception(error) return -errno.EINVAL, '', str(error) return 0, 'RGW credentials configured', '' diff --git a/src/pybind/mgr/dashboard/services/rgw_client.py b/src/pybind/mgr/dashboard/services/rgw_client.py index 48e50c38d8f3b..b055946d09e3b 100644 --- a/src/pybind/mgr/dashboard/services/rgw_client.py +++ b/src/pybind/mgr/dashboard/services/rgw_client.py @@ -5,9 +5,9 @@ import ipaddress import json import logging import re -import subprocess import xml.etree.ElementTree as ET # noqa: N814 from distutils.util import strtobool +from subprocess import SubprocessError from .. import mgr from ..awsauth import S3Auth @@ -29,7 +29,7 @@ class NoRgwDaemonsException(Exception): super().__init__('No RGW service is running.') -class NoCredentialsException(RequestException): +class NoCredentialsException(Exception): def __init__(self): super(NoCredentialsException, self).__init__( 'No RGW credentials found, ' @@ -194,86 +194,76 @@ def _parse_frontend_config(config) -> Tuple[int, bool]: raise LookupError('Failed to determine RGW port from "{}"'.format(config)) -def _radosgw_admin(args: List[str]) -> Tuple[int, str, str]: - try: - result = subprocess.run( # pylint: disable=subprocess-run-check - [ - 'radosgw-admin', - '-c', str(mgr.get_ceph_conf_path()), - '-k', str(mgr.get_ceph_option('keyring')), - '-n', f'mgr.{mgr.get_mgr_id()}', - ] + args, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - timeout=10, - ) - return result.returncode, result.stdout.decode('utf-8'), result.stderr.decode('utf-8') - except subprocess.CalledProcessError as ex: - mgr.log.error(f'Error executing radosgw-admin {ex.cmd}: {ex.output}') - raise - except subprocess.TimeoutExpired as ex: - mgr.log.error(f'Timeout (10s) executing radosgw-admin {ex.cmd}') - raise - - -def _parse_secrets(user: str, payload: str) -> Tuple[Optional[str], Optional[str]]: - data = json.loads(payload) +def _parse_secrets(user: str, data: dict) -> Tuple[str, str]: for key in data.get('keys', []): if key.get('user') == user and data.get('system') in ['true', True]: access_key = key.get('access_key') secret_key = key.get('secret_key') return access_key, secret_key - return None, None + return '', '' def _get_user_keys(user: str, realm: Optional[str] = None) -> Tuple[str, str]: - cmd = ['user', 'info', '--uid', user] + access_key = '' + secret_key = '' + rgw_user_info_cmd = ['user', 'info', '--uid', user] cmd_realm_option = ['--rgw-realm', realm] if realm else [] if realm: - cmd += cmd_realm_option - rc, out, err = _radosgw_admin(cmd) - access_key = None - secret_key = None - if not rc: - access_key, secret_key = _parse_secrets(user, out) - if not access_key: - rgw_create_user_cmd = [ - 'user', 'create', - '--uid', user, - '--display-name', 'Ceph Dashboard', - '--system', - ] + cmd_realm_option - rc, out, err = _radosgw_admin(rgw_create_user_cmd) - if not rc: + rgw_user_info_cmd += cmd_realm_option + try: + _, out, err = mgr.send_rgwadmin_command(rgw_user_info_cmd) + if out: access_key, secret_key = _parse_secrets(user, out) - if not access_key: - mgr.log.error(f'Unable to create rgw {user} user: {err}') - assert access_key - assert secret_key + if not access_key: + rgw_create_user_cmd = [ + 'user', 'create', + '--uid', user, + '--display-name', 'Ceph Dashboard', + '--system', + ] + cmd_realm_option + _, out, err = mgr.send_rgwadmin_command(rgw_create_user_cmd) + if out: + access_key, secret_key = _parse_secrets(user, out) + if not access_key: + logger.error('Unable to create rgw user "%s": %s', user, err) + except SubprocessError as error: + logger.exception(error) + return access_key, secret_key def configure_rgw_credentials(): - mgr.log.info('Configuring dashboard RGW credentials') + logger.info('Configuring dashboard RGW credentials') user = 'dashboard' - rc, out, err = _radosgw_admin(['realm', 'list']) - if rc: - error = RgwAdminException(f'Unable to list RGW realms: {err}') - mgr.log.exception(error) - raise error - j = json.loads(out) - realms = j.get('realms', []) - if realms: - access_keys = {} - secret_keys = {} - for realm in realms: - access_keys[realm], secret_keys[realm] = _get_user_keys(user, realm) - access_key = json.dumps(access_keys) - secret_key = json.dumps(secret_keys) - else: - access_key, secret_key = _get_user_keys(user) - Settings.RGW_API_ACCESS_KEY = access_key - Settings.RGW_API_SECRET_KEY = secret_key + realms = [] + access_key = '' + secret_key = '' + try: + _, out, err = mgr.send_rgwadmin_command(['realm', 'list']) + if out: + realms = out.get('realms', []) + if err: + logger.error('Unable to list RGW realms: %s', err) + if realms: + realm_access_keys = {} + realm_secret_keys = {} + for realm in realms: + realm_access_key, realm_secret_key = _get_user_keys(user, realm) + if realm_access_key: + realm_access_keys[realm] = realm_access_key + realm_secret_keys[realm] = realm_secret_key + if realm_access_keys: + access_key = json.dumps(realm_access_keys) + secret_key = json.dumps(realm_secret_keys) + else: + access_key, secret_key = _get_user_keys(user) + + assert access_key and secret_key + Settings.RGW_API_ACCESS_KEY = access_key + Settings.RGW_API_SECRET_KEY = secret_key + except (AssertionError, SubprocessError) as error: + logger.exception(error) + raise NoCredentialsException class RgwClient(RestClient): @@ -318,12 +308,9 @@ class RgwClient(RestClient): @staticmethod def _rgw_settings(): - return (Settings.RGW_API_HOST, - Settings.RGW_API_PORT, - Settings.RGW_API_ACCESS_KEY, + return (Settings.RGW_API_ACCESS_KEY, Settings.RGW_API_SECRET_KEY, Settings.RGW_API_ADMIN_RESOURCE, - Settings.RGW_API_SCHEME, Settings.RGW_API_SSL_VERIFY) @staticmethod @@ -335,30 +322,11 @@ class RgwClient(RestClient): # The API access key and secret key are mandatory for a minimal configuration. if not (Settings.RGW_API_ACCESS_KEY and Settings.RGW_API_SECRET_KEY): - try: - configure_rgw_credentials() - except Exception as error: - logger.exception(error) - raise NoCredentialsException() + configure_rgw_credentials() if not daemon_name: - # Select default daemon if configured in settings: - if Settings.RGW_API_HOST and Settings.RGW_API_PORT: - for daemon in RgwClient._daemons.values(): - if daemon.host == Settings.RGW_API_HOST \ - and daemon.port == Settings.RGW_API_PORT: - daemon_name = daemon.name - break - if not daemon_name: - raise DashboardException( - msg='No RGW daemon found with user-defined host: {}, port: {}'.format( - Settings.RGW_API_HOST, - Settings.RGW_API_PORT), - http_status_code=404, - component='rgw') # Select 1st daemon: - else: - daemon_name = next(iter(RgwClient._daemons.keys())) + daemon_name = next(iter(RgwClient._daemons.keys())) # Discard all cached instances if any rgw setting has changed if RgwClient._rgw_settings_snapshot != RgwClient._rgw_settings(): @@ -444,6 +412,7 @@ class RgwClient(RestClient): self.userid = self._get_user_id(self.admin_path) if self.got_keys_from_config \ else user_id except RequestException as error: + logger.exception(error) msg = 'Error connecting to Object Gateway' if error.status_code == 404: msg = '{}: {}'.format(msg, str(error)) diff --git a/src/pybind/mgr/dashboard/settings.py b/src/pybind/mgr/dashboard/settings.py index d76710a98e6e2..3b4a8e4eedc94 100644 --- a/src/pybind/mgr/dashboard/settings.py +++ b/src/pybind/mgr/dashboard/settings.py @@ -65,13 +65,9 @@ class Options(object): AUDIT_API_LOG_PAYLOAD = Setting(True, [bool]) # RGW settings - RGW_API_HOST = Setting('', [dict, str]) - RGW_API_PORT = Setting(80, [dict, int]) RGW_API_ACCESS_KEY = Setting('', [dict, str]) RGW_API_SECRET_KEY = Setting('', [dict, str]) RGW_API_ADMIN_RESOURCE = Setting('admin', [str]) - RGW_API_SCHEME = Setting('http', [str]) - RGW_API_USER_ID = Setting('', [dict, str]) RGW_API_SSL_VERIFY = Setting(True, [bool]) # Grafana settings diff --git a/src/pybind/mgr/dashboard/tests/__init__.py b/src/pybind/mgr/dashboard/tests/__init__.py index 59233bf4f5c3d..9075cdc65fc85 100644 --- a/src/pybind/mgr/dashboard/tests/__init__.py +++ b/src/pybind/mgr/dashboard/tests/__init__.py @@ -322,8 +322,6 @@ class RgwStub(Stub): @classmethod def get_settings(cls): settings = { - 'RGW_API_HOST': '', - 'RGW_API_PORT': 0, 'RGW_API_ACCESS_KEY': 'fake-access-key', 'RGW_API_SECRET_KEY': 'fake-secret-key', } diff --git a/src/pybind/mgr/dashboard/tests/test_rgw_client.py b/src/pybind/mgr/dashboard/tests/test_rgw_client.py index ff75fcf71c9a8..f8a8f2f689d2e 100644 --- a/src/pybind/mgr/dashboard/tests/test_rgw_client.py +++ b/src/pybind/mgr/dashboard/tests/test_rgw_client.py @@ -4,11 +4,12 @@ import errno from unittest import TestCase from unittest.mock import Mock, patch +from .. import mgr from ..exceptions import DashboardException from ..services.rgw_client import NoCredentialsException, \ NoRgwDaemonsException, RgwClient, _parse_frontend_config from ..settings import Settings -from . import CLICommandTestMixin, CmdException, RgwStub # pylint: disable=no-name-in-module +from . import CLICommandTestMixin, RgwStub # pylint: disable=no-name-in-module @patch('dashboard.services.rgw_client.RgwClient._get_user_id', Mock( @@ -19,19 +20,33 @@ class RgwClientTest(TestCase, CLICommandTestMixin): _dashboard_user_realm2_access_key = 'OMDR282VYLBC1ZYMYDL0' _dashboard_user_realm2_secret_key = 'N3thf7jAiwQ90PsPrhC2DIcvCFOsBXtBvPJJMdC3' _radosgw_admin_result_error = (-errno.EINVAL, '', 'fake error') - _radosgw_admin_result_no_realms = (0, '{}', '') - _radosgw_admin_result_realms = (0, '{"realms": ["realm1", "realm2"]}', '') - _radosgw_admin_result_user_realm1_payload = ( + _radosgw_admin_result_no_realms = (0, {}, '') + _radosgw_admin_result_realms = (0, {"realms": ["realm1", "realm2"]}, '') + _radosgw_admin_result_user_realm1 = ( 0, - '{"keys": [{"user": "dashboard", "access_key": "' - f'{_dashboard_user_realm1_access_key}",' - f'"secret_key": "{_dashboard_user_realm1_secret_key}"}}], "system": "true"}}', + { + "keys": [ + { + "user": "dashboard", + "access_key": _dashboard_user_realm1_access_key, + "secret_key": _dashboard_user_realm1_secret_key + } + ], + "system": "true" + }, '') - _radosgw_admin_result_user_realm2_payload = ( + _radosgw_admin_result_user_realm2 = ( 0, - '{"keys": [{"user": "dashboard", "access_key": "' - f'{_dashboard_user_realm2_access_key}",' - f'"secret_key": "{_dashboard_user_realm2_secret_key}"}}], "system": "true"}}', + { + "keys": [ + { + "user": "dashboard", + "access_key": _dashboard_user_realm2_access_key, + "secret_key": _dashboard_user_realm2_secret_key + } + ], + "system": "true" + }, '') def setUp(self): @@ -42,58 +57,87 @@ class RgwClientTest(TestCase, CLICommandTestMixin): 'RGW_API_SECRET_KEY': 'supergeheim', }) - @patch('dashboard.services.rgw_client._radosgw_admin') - def test_connect_rgw(self, radosgw_admin): - radosgw_admin.side_effect = [ - self._radosgw_admin_result_error, - self._radosgw_admin_result_no_realms, - self._radosgw_admin_result_user_realm1_payload - ] - with self.assertRaises(CmdException) as ctx: - self.exec_cmd('connect-rgw') - self.assertEqual(ctx.exception.retcode, -errno.EINVAL) - self.assertIn('Unable to list RGW realms', str(ctx.exception)) - - result = self.exec_cmd('connect-rgw') - self.assertEqual(result, 'RGW credentials configured') - self.assertEqual(Settings.RGW_API_ACCESS_KEY, self._dashboard_user_realm1_access_key) - self.assertEqual(Settings.RGW_API_SECRET_KEY, self._dashboard_user_realm1_secret_key) - def test_configure_credentials_error(self): self.CONFIG_KEY_DICT.update({ 'RGW_API_ACCESS_KEY': '', 'RGW_API_SECRET_KEY': '', }) + # Get no realms, get no user, user creation fails. + mgr.send_rgwadmin_command.side_effect = [ + self._radosgw_admin_result_error, + self._radosgw_admin_result_error, + self._radosgw_admin_result_error, + ] with self.assertRaises(NoCredentialsException) as cm: RgwClient.admin_instance() self.assertIn('No RGW credentials found', str(cm.exception)) - @patch('dashboard.services.rgw_client._radosgw_admin') - def test_configure_credentials_with_no_realms(self, radosgw_admin): + def test_configure_credentials_error_with_realms(self): self.CONFIG_KEY_DICT.update({ 'RGW_API_ACCESS_KEY': '', 'RGW_API_SECRET_KEY': '', }) - radosgw_admin.side_effect = [ - self._radosgw_admin_result_no_realms, - self._radosgw_admin_result_user_realm1_payload + # Get realms, get no user, user creation fails. + mgr.send_rgwadmin_command.side_effect = [ + self._radosgw_admin_result_realms, + self._radosgw_admin_result_error, + self._radosgw_admin_result_error, + self._radosgw_admin_result_error, + self._radosgw_admin_result_error, + ] + with self.assertRaises(NoCredentialsException) as cm: + RgwClient.admin_instance() + self.assertIn('No RGW credentials found', str(cm.exception)) + + def test_set_rgw_credentials_command(self): + # Get no realms, get user. + mgr.send_rgwadmin_command.side_effect = [ + self._radosgw_admin_result_error, + self._radosgw_admin_result_user_realm1 ] - RgwClient.admin_instance() + result = self.exec_cmd('set-rgw-credentials') + self.assertEqual(result, 'RGW credentials configured') self.assertEqual(Settings.RGW_API_ACCESS_KEY, self._dashboard_user_realm1_access_key) self.assertEqual(Settings.RGW_API_SECRET_KEY, self._dashboard_user_realm1_secret_key) - @patch('dashboard.services.rgw_client._radosgw_admin') - def test_configure_credentials_with_realms(self, radosgw_admin): - self.CONFIG_KEY_DICT.update({ - 'RGW_API_ACCESS_KEY': '', - 'RGW_API_SECRET_KEY': '', + # Get no realms, get no user, user creation. + mgr.send_rgwadmin_command.side_effect = [ + self._radosgw_admin_result_error, + self._radosgw_admin_result_error, + self._radosgw_admin_result_user_realm1 + ] + result = self.exec_cmd('set-rgw-credentials') + self.assertEqual(result, 'RGW credentials configured') + self.assertEqual(Settings.RGW_API_ACCESS_KEY, self._dashboard_user_realm1_access_key) + self.assertEqual(Settings.RGW_API_SECRET_KEY, self._dashboard_user_realm1_secret_key) + + # Get realms, get users. + mgr.send_rgwadmin_command.side_effect = [ + self._radosgw_admin_result_realms, + self._radosgw_admin_result_user_realm1, + self._radosgw_admin_result_user_realm2 + ] + result = self.exec_cmd('set-rgw-credentials') + self.assertEqual(result, 'RGW credentials configured') + self.assertEqual(Settings.RGW_API_ACCESS_KEY, { + 'realm1': self._dashboard_user_realm1_access_key, + 'realm2': self._dashboard_user_realm2_access_key }) - radosgw_admin.side_effect = [ + self.assertEqual(Settings.RGW_API_SECRET_KEY, { + 'realm1': self._dashboard_user_realm1_secret_key, + 'realm2': self._dashboard_user_realm2_secret_key + }) + + # Get realms, get no users, users' creation. + mgr.send_rgwadmin_command.side_effect = [ self._radosgw_admin_result_realms, - self._radosgw_admin_result_user_realm1_payload, - self._radosgw_admin_result_user_realm2_payload + self._radosgw_admin_result_error, + self._radosgw_admin_result_user_realm1, + self._radosgw_admin_result_error, + self._radosgw_admin_result_user_realm2 ] - RgwClient.admin_instance() + result = self.exec_cmd('set-rgw-credentials') + self.assertEqual(result, 'RGW credentials configured') self.assertEqual(Settings.RGW_API_ACCESS_KEY, { 'realm1': self._dashboard_user_realm1_access_key, 'realm2': self._dashboard_user_realm2_access_key @@ -103,6 +147,23 @@ class RgwClientTest(TestCase, CLICommandTestMixin): 'realm2': self._dashboard_user_realm2_secret_key }) + # Get realms, get no users, realm 2 user creation fails. + mgr.send_rgwadmin_command.side_effect = [ + self._radosgw_admin_result_realms, + self._radosgw_admin_result_error, + self._radosgw_admin_result_user_realm1, + self._radosgw_admin_result_error, + self._radosgw_admin_result_error, + ] + result = self.exec_cmd('set-rgw-credentials') + self.assertEqual(result, 'RGW credentials configured') + self.assertEqual(Settings.RGW_API_ACCESS_KEY, { + 'realm1': self._dashboard_user_realm1_access_key, + }) + self.assertEqual(Settings.RGW_API_SECRET_KEY, { + 'realm1': self._dashboard_user_realm1_secret_key, + }) + def test_ssl_verify(self): Settings.RGW_API_SSL_VERIFY = True instance = RgwClient.admin_instance() @@ -119,15 +180,6 @@ class RgwClientTest(TestCase, CLICommandTestMixin): RgwClient.admin_instance() self.assertIn('No RGW service is running.', str(cm.exception)) - def test_default_daemon_wrong_settings(self): - self.CONFIG_KEY_DICT.update({ - 'RGW_API_HOST': '172.20.0.2', - 'RGW_API_PORT': '7990', - }) - with self.assertRaises(DashboardException) as cm: - RgwClient.admin_instance() - self.assertIn('No RGW daemon found with user-defined host:', str(cm.exception)) - @patch.object(RgwClient, '_get_daemon_zone_info') def test_get_placement_targets_from_zone(self, zone_info): zone_info.return_value = { diff --git a/src/pybind/mgr/mgr_module.py b/src/pybind/mgr/mgr_module.py index 593db937645b2..ecc46fe8f9699 100644 --- a/src/pybind/mgr/mgr_module.py +++ b/src/pybind/mgr/mgr_module.py @@ -14,6 +14,7 @@ import logging import errno import functools import json +import subprocess import threading from collections import defaultdict from enum import IntEnum @@ -1838,3 +1839,33 @@ class MgrModule(ceph_module.BaseMgrModule, MgrModuleLoggingMixin): :param arguments: dict of key/value arguments to test """ return self._ceph_is_authorized(arguments) + + def send_rgwadmin_command(self, args: List[str], + stdout_as_json: bool = True) -> Tuple[int, Union[str, dict], str]: + try: + cmd = [ + 'radosgw-admin', + '-c', str(self.get_ceph_conf_path()), + '-k', str(self.get_ceph_option('keyring')), + '-n', f'mgr.{self.get_mgr_id()}', + ] + args + self.log.debug('Executing %s', str(cmd)) + result = subprocess.run( # pylint: disable=subprocess-run-check + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=10, + ) + stdout = result.stdout.decode('utf-8') + stderr = result.stderr.decode('utf-8') + if stdout and stdout_as_json: + stdout = json.loads(stdout) + if result.returncode: + self.log.debug('Error %s executing %s: %s', result.returncode, str(cmd), stderr) + return result.returncode, stdout, stderr + except subprocess.CalledProcessError as ex: + self.log.exception('Error executing radosgw-admin %s: %s', str(ex.cmd), str(ex.output)) + raise + except subprocess.TimeoutExpired as ex: + self.log.error('Timeout (10s) executing radosgw-admin %s', str(ex.cmd)) + raise -- 2.39.5