]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: select default daemon based on the default zonegroup
authorNizamudeen A <nia@redhat.com>
Fri, 7 Jun 2024 07:45:06 +0000 (13:15 +0530)
committerNizamudeen A <nia@redhat.com>
Fri, 21 Jun 2024 11:15:50 +0000 (16:45 +0530)
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 <nia@redhat.com>
qa/tasks/mgr/dashboard/test_rgw.py
src/pybind/mgr/dashboard/services/rgw_client.py
src/pybind/mgr/dashboard/tests/__init__.py
src/pybind/mgr/dashboard/tests/test_rgw.py

index 01dbae59febafc83733c6a4ba25b540f0d3a9172..5c7b032967589e0aa8230fa26697f37928dd6e30 100644 (file)
@@ -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'])
index 0d7df8e31e879f93b8180a33ab0d2e958be36f16..3872c9073ac610698e613cadb153ee9253eb54a6 100644 (file)
@@ -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':
index ece3ef721bf76ab3e4b97541949beb14d47647fb..3061fe9dc93167ec2e126f383bd520825640540f 100644 (file)
@@ -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'
                 }
index 6e7a2960644aac8c2c6fc434cda744942d91ccf0..d01187c4e247a9a13032ddf9ac46a1adee51d431 100644 (file)
@@ -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