]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: connect-rgw: rename to set-rgw-credentials; refactoring
authorAlfonso Martínez <almartin@redhat.com>
Fri, 6 Aug 2021 06:57:47 +0000 (08:57 +0200)
committerAlfonso Martínez <almartin@redhat.com>
Tue, 10 Aug 2021 12:06:03 +0000 (14:06 +0200)
- 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 <almartin@redhat.com>
doc/mgr/dashboard.rst
qa/tasks/mgr/dashboard/test_rgw.py
qa/tasks/mgr/dashboard/test_settings.py
src/pybind/mgr/cephadm/serve.py
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.e2e-spec.ts
src/pybind/mgr/dashboard/module.py
src/pybind/mgr/dashboard/services/rgw_client.py
src/pybind/mgr/dashboard/settings.py
src/pybind/mgr/dashboard/tests/__init__.py
src/pybind/mgr/dashboard/tests/test_rgw_client.py
src/pybind/mgr/mgr_module.py

index 7dd324370760868d2d9405a092486527e3f23f37..abc8cf9eee5dc3027f05676f65bc7d49190274a1 100644 (file)
@@ -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 <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
index f545c748329062b0b88ae3b68a68116aa31a4b06..b7d650f41602056d54e88595aee78e955b7a6f0d 100644 (file)
@@ -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)
index 46750292e1c9447d002a9989f9731b42ff924d23..869ea2ca445fb8b4de88e66791ee536a1739b424 100644 (file)
@@ -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)
 
index 26c4683fe2b7bacc3ca541bcc4215aff84ec6b1c..0cda64145cb9edd0e82808345f62a15802833712 100644 (file)
@@ -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()
index e3b7ce3599fb3dfd21a050738d678182064bf99c..0a2aa8184ceee2f77eb9255487d11aa6e059e280 100644 (file)
@@ -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',
index 62b0c4c922384d759287e3b46df0fa168dd77e68..a42c4a59e39c1323eeceec2871b3f84f60df6296 100644 (file)
@@ -407,12 +407,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', ''
index af1f13af6a6a069eea4093b7273b93906ee763e0..7fd7ae236745eb8c3131e5b2b4f8dd07cac29161 100644 (file)
@@ -4,9 +4,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
@@ -28,7 +28,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, '
@@ -193,86 +193,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):
@@ -317,12 +307,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
@@ -334,30 +321,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():
@@ -443,6 +411,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))
index 141555b4707fabe3d3b87410d1d4b9dc5ddcb064..72a0f815a6552d39a081515b5ebdb31ff5641408 100644 (file)
@@ -63,13 +63,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
index f755a69faf8a8e2e44a0d59627c3da9156203d98..b14c54c82edddfa89ed6499af03f25c5b342ca9d 100644 (file)
@@ -297,8 +297,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',
         }
index ff75fcf71c9a8a1af248c2ed34f75ced59e3302e..f8a8f2f689d2ebab6e274700561015d9b9f01bfe 100644 (file)
@@ -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 = {
index 76fded798e4719221657b519d1af626f7d47794b..1c6a134e2086e3fcbc103fbb5e154299358060ad 100644 (file)
@@ -14,6 +14,7 @@ import logging
 import errno
 import functools
 import json
+import subprocess
 import threading
 from collections import defaultdict
 from enum import IntEnum
@@ -2050,3 +2051,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