mgr/dashboard: fix unncessary traceback when bucket not exist

UI has an async validator which calls the GET bucket API to make sure
the bucket name doesn't exist, but the proxy
was not properly handling the http_status_codes which results in raising
a massive traceback in logs whenever you type things in the bucket name
field. So handling that gracefully by capturing the proper status codes
for both RequestException and DashboardException

BEFORE
```
File "/usr/share/ceph/mgr/dashboard/services/exception.py", line 47, in dashboard_exception_handler
return handler(*args, **kwargs)
File "/lib/python3.9/site-packages/cherrypy/_cpdispatch.py", line 54, in _call_
return self.callable(*self.args, **self.kwargs)
File "/usr/share/ceph/mgr/dashboard/controllers/_base_controller.py", line 263, in inner
ret = func(*args, **kwargs)
File "/usr/share/ceph/mgr/dashboard/controllers/_rest_controller.py", line 193, in wrapper
return func(*vpath, **params)
File "/usr/share/ceph/mgr/dashboard/controllers/rgw.py", line 357, in get
result = self.proxy(daemon_name, 'GET', 'bucket', {'bucket': bucket})
File "/usr/share/ceph/mgr/dashboard/controllers/rgw.py", line 213, in proxy
raise DashboardException(e, http_status_code=http_status_code, component='rgw')
dashboard.exceptions.DashboardException: RGW REST API failed request with status code 404
(b'{"Code":"NoSuchBucket","Message":"","RequestId":"tx00000f14e08c1af0d5615-006'
b'71f54a7-3bc6-default","HostId":"3bc6-default-default"}')
2024-10-28T09:08:55.990+0000 7f89e045b640  0 [dashboard INFO request] [::ffff:10.74.18.122:51853] [GET] [500] [0.007s] [admin] [200.0B] /api/rgw/bucket/bucket-das
```

AFTER
```
Jul 08 09:28:28 ceph-node-00 ceph-mgr[2243]: [dashboard ERROR dashboard.rest_client] RGW REST API failed GET req status: 404
Jul 08 09:28:28 ceph-node-00 ceph-mgr[2243]: [dashboard INFO dashboard.services.exception] Dashboard Exception: RGW REST API failed request with status code 404
                                             (b'{"Code":"NoSuchBucket","Message":"","RequestId":"tx00000c029a81e5d154844-006'
                                              b'a4e183c-14251-default","HostId":"14251-default-default"}')
Jul 08 09:28:28 ceph-node-00 ceph-mgr[2243]: [dashboard INFO dashboard.tools] [::ffff:192.168.100.1:60408] [GET] [404] [0.078s] [admin] [200.0B] /api/rgw/bucket/testsa
```

Fixes: https://tracker.ceph.com/issues/78038
Signed-off-by: Nizamudeen A <nia@redhat.com>
This commit is contained in:
Nizamudeen A 2026-07-08 14:57:33 +05:30
parent dd7b7812d3
commit 0ad33c0965
3 changed files with 56 additions and 4 deletions

View File

@ -550,7 +550,7 @@ class RgwUserTest(RgwTestCase):
self._delete('/api/rgw/user/teuth-test-user')
self.assertStatus(204)
self.get_rgw_user('teuth-test-user')
self.assertStatus(500)
self.assertStatus(404)
resp = self.jsonBody()
self.assertIn('detail', resp)
self.assertIn('failed request with status code 404', resp['detail'])
@ -593,7 +593,7 @@ class RgwUserTest(RgwTestCase):
self._delete('/api/rgw/user/test01$teuth-test-user')
self.assertStatus(204)
self.get_rgw_user('test01$teuth-test-user')
self.assertStatus(500)
self.assertStatus(404)
resp = self.jsonBody()
self.assertIn('detail', resp)
self.assertIn('failed request with status code 404', resp['detail'])

View File

@ -394,7 +394,11 @@ class RgwRESTController(RESTController):
result = json_str_to_object(result)
return result
except (DashboardException, RequestException) as e:
http_status_code = e.status if isinstance(e, DashboardException) else 500
response = getattr(e, 'response', None)
if isinstance(e, RequestException) and response is not None:
http_status_code = getattr(response, 'status_code', 500)
else:
http_status_code = getattr(e, 'status_code', getattr(e, 'status', None)) or 500
raise DashboardException(e, http_status_code=http_status_code, component='rgw')

View File

@ -2,7 +2,7 @@
from unittest.mock import Mock, call, patch
from .. import mgr
from ..controllers.rgw import Rgw, RgwDaemon, RgwTopic, RgwUser
from ..controllers.rgw import Rgw, RgwBucket, RgwDaemon, RgwTopic, RgwUser
from ..rest_client import RequestException
from ..services.rgw_client import RgwClient, RgwMultisite
from ..tests import ControllerTestCase, RgwStub
@ -600,3 +600,51 @@ class TestRgwTopicController(ControllerTestCase):
result = controller.delete(key='RGW22222222222222222:HttpTest')
mock_delete_topic.assert_called_with(key='RGW22222222222222222:HttpTest')
self.assertEqual(result, None)
class RgwBucketControllerTestCase(ControllerTestCase):
@classmethod
def setup_server(cls):
cls.setup_controllers([RgwBucket], '/test')
@patch('dashboard.services.rgw_client.RgwClient.admin_instance')
def test_get_bucket_not_found(self, mock_admin_instance):
mock_instance = Mock()
mock_admin_instance.return_value = mock_instance
mock_response = Mock()
mock_response.status_code = 404
mock_exception = RequestException('RGW REST API failed request')
mock_exception.response = mock_response
mock_instance.proxy.side_effect = mock_exception
self._get('/test/api/rgw/bucket/i-do-not-exist')
self.assertStatus(404)
@patch('dashboard.services.rgw_client.RgwClient.admin_instance')
def test_get_bucket_not_found_with_fallback_attribute(self, mock_admin_instance):
mock_instance = Mock()
mock_admin_instance.return_value = mock_instance
# mock that lacks a .response but has a direct .status_code attribute.
mock_exception = RequestException('RGW REST API failed request')
mock_exception.status_code = 404
mock_instance.proxy.side_effect = mock_exception
self._get('/test/api/rgw/bucket/i-do-not-exist')
self.assertStatus(404)
@patch('dashboard.services.rgw_client.RgwClient.admin_instance')
def test_get_bucket_server_error(self, mock_admin_instance):
mock_instance = Mock()
mock_admin_instance.return_value = mock_instance
mock_exception = RequestException('Internal Server Error')
mock_exception.status_code = 500
mock_instance.proxy.side_effect = mock_exception
self._get('/test/api/rgw/bucket/i-do-not-exist')
self.assertStatus(500)