From: Nizamudeen A Date: Fri, 7 Jun 2024 07:45:06 +0000 (+0530) Subject: mgr/dashboard: select default daemon based on the default zonegroup X-Git-Tag: testing/wip-jcollin-testing-20240711.095637-squid~10^2~2 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=4dfeee6006b4a3575ea70e435e787bd41d2a0ead;p=ceph-ci.git mgr/dashboard: select default daemon based on the default zonegroup if multisite is configured, the default daemon needs to be selected based on the default zonegroup. Otherwise dashboard gives you incorrect details when doing the period commit The issue occurs when you do a period update --commit and you reload one of the block page, the api assigns the zonegroup of the second gateway because for a moment, the first gateway reflects the period changes... This is not true because the default zonegroup is of the previous active gateway but even though the back-end correctly says the active zonegroup, the dashboard api says it wrongly. Fixes: https://tracker.ceph.com/issues/66394 Signed-off-by: Nizamudeen A (cherry picked from commit 013d0826c12e4bfc9dc2b8984b1605694ac7d2ea) --- diff --git a/qa/tasks/mgr/dashboard/test_rgw.py b/qa/tasks/mgr/dashboard/test_rgw.py index 01dbae59feb..5c7b0329675 100644 --- a/qa/tasks/mgr/dashboard/test_rgw.py +++ b/qa/tasks/mgr/dashboard/test_rgw.py @@ -66,31 +66,6 @@ class RgwTestCase(DashboardTestCase): return self._get('/api/rgw/user/{}?stats={}'.format(uid, stats)) -class RgwApiCredentialsTest(RgwTestCase): - - AUTH_ROLES = ['rgw-manager'] - - 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(404) - self.assertIn('detail', resp) - self.assertIn('component', resp) - 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('/ui-api/rgw/status') - self.assertStatus(200) - self.assertIn('available', data) - self.assertIn('message', data) - self.assertTrue(data['available']) - - class RgwSiteTest(RgwTestCase): AUTH_ROLES = ['rgw-manager'] @@ -124,9 +99,14 @@ class RgwBucketTest(RgwTestCase): def setUpClass(cls): cls.create_test_user = True super(RgwBucketTest, cls).setUpClass() + # Create an MFA user + cls._radosgw_admin_cmd([ + 'user', 'create', '--uid', 'mfa-test-user', '--display-name', 'mfa-user', + '--system', '--access-key', 'mfa-access', '--secret', 'mfa-secret' + ]) # Create MFA TOTP token for test user. cls._radosgw_admin_cmd([ - 'mfa', 'create', '--uid', 'teuth-test-user', '--totp-serial', cls._mfa_token_serial, + 'mfa', 'create', '--uid', 'mfa-test-user', '--totp-serial', cls._mfa_token_serial, '--totp-seed', cls._mfa_token_seed, '--totp-seed-type', 'base32', '--totp-seconds', str(cls._mfa_token_time_step), '--totp-window', '1' ]) @@ -231,7 +211,7 @@ class RgwBucketTest(RgwTestCase): '/api/rgw/bucket/teuth-test-bucket', params={ 'bucket_id': data['id'], - 'uid': 'teuth-test-user', + 'uid': 'mfa-test-user', 'versioning_state': 'Enabled' }) self.assertStatus(200) @@ -242,7 +222,7 @@ class RgwBucketTest(RgwTestCase): 'bid': JLeaf(str), 'tenant': JLeaf(str) }, allow_unknown=True)) - self.assertEqual(data['owner'], 'teuth-test-user') + self.assertEqual(data['owner'], 'mfa-test-user') self.assertEqual(data['versioning'], 'Enabled') # Update bucket: enable MFA Delete. @@ -250,7 +230,7 @@ class RgwBucketTest(RgwTestCase): '/api/rgw/bucket/teuth-test-bucket', params={ 'bucket_id': data['id'], - 'uid': 'teuth-test-user', + 'uid': 'mfa-test-user', 'versioning_state': 'Enabled', 'mfa_delete': 'Enabled', 'mfa_token_serial': self._mfa_token_serial, @@ -268,7 +248,7 @@ class RgwBucketTest(RgwTestCase): '/api/rgw/bucket/teuth-test-bucket', params={ 'bucket_id': data['id'], - 'uid': 'teuth-test-user', + 'uid': 'mfa-test-user', 'versioning_state': 'Suspended', 'mfa_delete': 'Disabled', 'mfa_token_serial': self._mfa_token_serial, @@ -388,7 +368,7 @@ class RgwBucketTest(RgwTestCase): self._post('/api/rgw/bucket', params={ 'bucket': 'teuth-test-bucket', - 'uid': 'teuth-test-user', + 'uid': 'mfa-test-user', 'zonegroup': 'default', 'placement_target': 'default-placement', 'lock_enabled': 'true', @@ -417,7 +397,7 @@ class RgwBucketTest(RgwTestCase): self._put('/api/rgw/bucket/teuth-test-bucket', params={ 'bucket_id': data['id'], - 'uid': 'teuth-test-user', + 'uid': 'mfa-test-user', 'lock_mode': 'COMPLIANCE', 'lock_retention_period_days': '15', 'lock_retention_period_years': '0' @@ -434,7 +414,7 @@ class RgwBucketTest(RgwTestCase): self._put('/api/rgw/bucket/teuth-test-bucket', params={ 'bucket_id': data['id'], - 'uid': 'teuth-test-user', + 'uid': 'mfa-test-user', 'versioning_state': 'Suspended' }) self.assertStatus(409) @@ -866,3 +846,28 @@ class RgwUserSubuserTest(RgwTestCase): key = self.find_object_in_list( 'user', 'teuth-test-user:teuth-test-subuser', data['keys']) self.assertIsInstance(key, object) + + +class RgwApiCredentialsTest(RgwTestCase): + + AUTH_ROLES = ['rgw-manager'] + + 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(404) + self.assertIn('detail', resp) + self.assertIn('component', resp) + 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('/ui-api/rgw/status') + self.assertStatus(200) + self.assertIn('available', data) + self.assertIn('message', data) + self.assertTrue(data['available']) diff --git a/src/pybind/mgr/dashboard/services/rgw_client.py b/src/pybind/mgr/dashboard/services/rgw_client.py index 0d7df8e31e8..3872c9073ac 100644 --- a/src/pybind/mgr/dashboard/services/rgw_client.py +++ b/src/pybind/mgr/dashboard/services/rgw_client.py @@ -59,6 +59,7 @@ class RgwDaemon: ssl: bool realm_name: str zonegroup_name: str + zonegroup_id: str zone_name: str @@ -78,6 +79,7 @@ def _get_daemons() -> Dict[str, RgwDaemon]: daemon.name = daemon_map[key]['metadata']['id'] daemon.realm_name = daemon_map[key]['metadata']['realm_name'] daemon.zonegroup_name = daemon_map[key]['metadata']['zonegroup_name'] + daemon.zonegroup_id = daemon_map[key]['metadata']['zonegroup_id'] daemon.zone_name = daemon_map[key]['metadata']['zone_name'] daemons[daemon.name] = daemon logger.info('Found RGW daemon with configuration: host=%s, port=%d, ssl=%s', @@ -348,9 +350,24 @@ class RgwClient(RestClient): if not (Settings.RGW_API_ACCESS_KEY and Settings.RGW_API_SECRET_KEY): configure_rgw_credentials() + daemon_keys = RgwClient._daemons.keys() if not daemon_name: - # Select 1st daemon: - daemon_name = next(iter(RgwClient._daemons.keys())) + if len(daemon_keys) > 1: + try: + multiiste = RgwMultisite() + default_zonegroup = multiiste.get_all_zonegroups_info()['default_zonegroup'] + + # Iterate through _daemons.values() to find the daemon with the + # matching zonegroup_id + for daemon in RgwClient._daemons.values(): + if daemon.zonegroup_id == default_zonegroup: + daemon_name = daemon.name + break + except Exception: # pylint: disable=broad-except + daemon_name = next(iter(daemon_keys)) + else: + # Handle the case where there is only one or no key in _daemons + daemon_name = next(iter(daemon_keys)) # Discard all cached instances if any rgw setting has changed if RgwClient._rgw_settings_snapshot != RgwClient._rgw_settings(): @@ -358,29 +375,29 @@ class RgwClient(RestClient): RgwClient.drop_instance() if daemon_name not in RgwClient._config_instances: - connection_info = RgwClient._get_daemon_connection_info(daemon_name) - RgwClient._config_instances[daemon_name] = RgwClient(connection_info['access_key'], + connection_info = RgwClient._get_daemon_connection_info(daemon_name) # type: ignore + RgwClient._config_instances[daemon_name] = RgwClient(connection_info['access_key'], # type: ignore # noqa E501 #pylint: disable=line-too-long connection_info['secret_key'], - daemon_name) + daemon_name) # type: ignore - if not userid or userid == RgwClient._config_instances[daemon_name].userid: - return RgwClient._config_instances[daemon_name] + if not userid or userid == RgwClient._config_instances[daemon_name].userid: # type: ignore + return RgwClient._config_instances[daemon_name] # type: ignore if daemon_name not in RgwClient._user_instances \ or userid not in RgwClient._user_instances[daemon_name]: # Get the access and secret keys for the specified user. - keys = RgwClient._config_instances[daemon_name].get_user_keys(userid) + keys = RgwClient._config_instances[daemon_name].get_user_keys(userid) # type: ignore if not keys: raise RequestException( "User '{}' does not have any keys configured.".format( userid)) instance = RgwClient(keys['access_key'], keys['secret_key'], - daemon_name, + daemon_name, # type: ignore userid) - RgwClient._user_instances.update({daemon_name: {userid: instance}}) + RgwClient._user_instances.update({daemon_name: {userid: instance}}) # type: ignore - return RgwClient._user_instances[daemon_name][userid] + return RgwClient._user_instances[daemon_name][userid] # type: ignore @staticmethod def admin_instance(daemon_name: Optional[str] = None) -> 'RgwClient': diff --git a/src/pybind/mgr/dashboard/tests/__init__.py b/src/pybind/mgr/dashboard/tests/__init__.py index ece3ef721bf..3061fe9dc93 100644 --- a/src/pybind/mgr/dashboard/tests/__init__.py +++ b/src/pybind/mgr/dashboard/tests/__init__.py @@ -302,6 +302,7 @@ class RgwStub(Stub): 'id': 'daemon1', 'realm_name': 'realm1', 'zonegroup_name': 'zonegroup1', + 'zonegroup_id': 'zonegroup1-id', 'zone_name': 'zone1', 'hostname': 'daemon1.server.lan' } @@ -313,6 +314,7 @@ class RgwStub(Stub): 'id': 'daemon2', 'realm_name': 'realm2', 'zonegroup_name': 'zonegroup2', + 'zonegroup_id': 'zonegroup2-id', 'zone_name': 'zone2', 'hostname': 'daemon2.server.lan' } diff --git a/src/pybind/mgr/dashboard/tests/test_rgw.py b/src/pybind/mgr/dashboard/tests/test_rgw.py index 58403e18c36..bb3006f5dc7 100644 --- a/src/pybind/mgr/dashboard/tests/test_rgw.py +++ b/src/pybind/mgr/dashboard/tests/test_rgw.py @@ -3,7 +3,7 @@ from unittest.mock import Mock, call, patch from .. import mgr from ..controllers.rgw import Rgw, RgwDaemon, RgwUser from ..rest_client import RequestException -from ..services.rgw_client import RgwClient +from ..services.rgw_client import RgwClient, RgwMultisite from ..tests import ControllerTestCase, RgwStub @@ -203,6 +203,105 @@ class RgwDaemonControllerTestCase(ControllerTestCase): self.assertStatus(200) self.assertJsonBody([]) + @patch('dashboard.services.rgw_client.RgwClient._get_user_id', Mock( + return_value='dummy_admin')) + @patch('dashboard.services.ceph_service.CephService.send_command') + @patch.object(RgwMultisite, 'get_all_zonegroups_info', Mock( + return_value={'default_zonegroup': 'zonegroup2-id'})) + def test_default_zonegroup_when_multiple_daemons(self, send_command): + send_command.return_value = '' + RgwStub.get_daemons() + RgwStub.get_settings() + metadata_return_values = [ + { + 'ceph_version': 'ceph version master (dev)', + 'id': 'daemon1', + 'realm_name': 'realm1', + 'zonegroup_name': 'zg1', + 'zonegroup_id': 'zg1-id', + 'zone_name': 'zone1', + 'frontend_config#0': 'beast port=80' + }, + { + 'ceph_version': 'ceph version master (dev)', + 'id': 'daemon2', + 'realm_name': 'realm2', + 'zonegroup_name': 'zg2', + 'zonegroup_id': 'zg2-id', + 'zone_name': 'zone2', + 'frontend_config#0': 'beast ssl_port=443' + } + ] + list_servers_return_value = [{ + 'hostname': 'host1', + 'services': [ + {'id': '5297', 'type': 'rgw'}, + {'id': '5356', 'type': 'rgw'}, + ] + }] + + mgr.list_servers.return_value = list_servers_return_value + mgr.get_metadata.side_effect = metadata_return_values + self._get('/test/api/rgw/daemon') + self.assertStatus(200) + + self.assertJsonBody([{ + 'id': 'daemon1', + 'service_map_id': '5297', + 'version': 'ceph version master (dev)', + 'server_hostname': 'host1', + 'realm_name': 'realm1', + 'zonegroup_name': 'zg1', + 'zonegroup_id': 'zg1-id', + 'zone_name': 'zone1', + 'default': False, + 'port': 80 + }, + { + 'id': 'daemon2', + 'service_map_id': '5356', + 'version': 'ceph version master (dev)', + 'server_hostname': 'host1', + 'realm_name': 'realm2', + 'zonegroup_name': 'zg2', + 'zonegroup_id': 'zg2-id', + 'zone_name': 'zone2', + 'default': True, + 'port': 443, + }]) + + # Change the default zonegroup and test if the correct daemon gets picked up + RgwMultisite().get_all_zonegroups_info.return_value = {'default_zonegroup': 'zonegroup1-id'} + mgr.list_servers.return_value = list_servers_return_value + mgr.get_metadata.side_effect = metadata_return_values + self._get('/test/api/rgw/daemon') + self.assertStatus(200) + + self.assertJsonBody([{ + 'id': 'daemon1', + 'service_map_id': '5297', + 'version': 'ceph version master (dev)', + 'server_hostname': 'host1', + 'realm_name': 'realm1', + 'zonegroup_name': 'zg1', + 'zonegroup_id': 'zg1-id', + 'zone_name': 'zone1', + 'default': True, + 'port': 80 + }, + { + 'id': 'daemon2', + 'service_map_id': '5356', + 'version': 'ceph version master (dev)', + 'server_hostname': 'host1', + 'realm_name': 'realm2', + 'zonegroup_name': 'zg2', + 'zonegroup_id': 'zg2-id', + 'zone_name': 'zone2', + 'default': False, + 'port': 443, + }]) + class RgwUserControllerTestCase(ControllerTestCase): @classmethod