-
Notifications
You must be signed in to change notification settings - Fork 6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
mgr/dashboard: Improve exception handling #21066
Changes from all commits
7f16fb1
dc616c7
c08999a
96f2196
a49f39f
3a34bae
b4c9b69
c43a1b4
6487b4b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -260,9 +260,12 @@ def test_create_rbd_twice(self): | |
res = self.create_image('rbd', 'test_rbd_twice', 10240) | ||
|
||
res = self.create_image('rbd', 'test_rbd_twice', 10240) | ||
self.assertStatus(409) | ||
self.assertEqual(res, {"errno": 17, "status": 409, "component": "rbd", | ||
"detail": "[errno 17] error creating image"}) | ||
self.assertStatus(400) | ||
self.assertEqual(res, {"code": '17', 'status': 400, "component": "rbd", | ||
"detail": "[errno 17] error creating image", | ||
'task': {'name': 'rbd/create', | ||
'metadata': {'pool_name': 'rbd', | ||
'image_name': 'test_rbd_twice'}}}) | ||
self.remove_image('rbd', 'test_rbd_twice') | ||
self.assertStatus(204) | ||
|
||
|
@@ -316,9 +319,12 @@ def test_disk_usage(self): | |
|
||
def test_delete_non_existent_image(self): | ||
res = self.remove_image('rbd', 'i_dont_exist') | ||
self.assertStatus(409) | ||
self.assertEqual(res, {"errno": 2, "status": 409, "component": "rbd", | ||
"detail": "[errno 2] error removing image"}) | ||
self.assertStatus(400) | ||
self.assertEqual(res, {u'code': u'2', "status": 400, "component": "rbd", | ||
"detail": "[errno 2] error removing image", | ||
'task': {'name': 'rbd/delete', | ||
'metadata': {'pool_name': 'rbd', | ||
'image_name': 'i_dont_exist'}}}) | ||
|
||
def test_image_delete(self): | ||
self.create_image('rbd', 'delete_me', 2**30) | ||
|
@@ -472,9 +478,9 @@ def test_clone(self): | |
'snap_name': 'snap1'}) | ||
|
||
res = self.remove_image('rbd', 'cimg') | ||
self.assertStatus(409) | ||
self.assertIn('errno', res) | ||
self.assertEqual(res['errno'], 39) | ||
self.assertStatus(400) | ||
self.assertIn('code', res) | ||
self.assertEqual(res['code'], '39') | ||
|
||
self.remove_image('rbd', 'cimg-clone') | ||
self.assertStatus(204) | ||
|
@@ -525,7 +531,7 @@ def test_flatten(self): | |
self.assertIsNotNone(img['parent']) | ||
|
||
self.flatten_image('rbd_iscsi', 'img1_snapf_clone') | ||
self.assertStatus(200) | ||
self.assertStatus([200, 201]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The only status code returned by this operation is 200 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to https://github.com/ceph/ceph/pull/21066/files#diff-52895dc8193e5a52e6bebbdcb34b9bbdR192 it can also return 201 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @sebastian-philipp you're right |
||
|
||
img = self._get('/api/block/image/rbd_iscsi/img1_snapf_clone') | ||
self.assertStatus(200) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -905,3 +905,82 @@ Usage example: | |
}) | ||
} | ||
} | ||
|
||
Error Handling in Python | ||
~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
Good error handling is a key requirement in creating a good user experience | ||
and providing a good API. | ||
|
||
Dashboard code should not duplicate C++ code. Thus, if error handling in C++ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a suggestion: "The Dashboard code should not duplicate the C++ code. Thus, if the error handling in C++ is sufficient, we shouldn't build your own wrapper. On the other hand, input validation is the best place to catch errors and generate the best error messages. If required, generate errors as soon as possible." There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed. |
||
is sufficient to provide good feedback, a new wrapper to catch these errors | ||
is not necessary. On the other hand, input validation is the best place to | ||
catch errors and generate the best error messages. If required, generate | ||
errors as soon as possible. | ||
|
||
The backend provides few standard ways of returning errors. | ||
|
||
First, there is a generic Internal Server Error:: | ||
|
||
Status Code: 500 | ||
{ | ||
"version": <cherrypy version, e.g. 13.1.0>, | ||
"detail": "The server encountered an unexpected condition which prevented it from fulfilling the request.", | ||
} | ||
|
||
|
||
For errors generated by the backend, we provide a standard error | ||
format:: | ||
|
||
Status Code: 400 | ||
{ | ||
"detail": str(e), # E.g. "[errno -42] <some error message>" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed. |
||
"component": "rbd", # this can be null to represent a global error code | ||
"code": "3", # Or a error name, e.g. "code": "some_error_key" | ||
} | ||
|
||
|
||
In case, the API Endpoints uses @ViewCache to temporarily cache results, | ||
the error looks like so:: | ||
|
||
Status Code 400 | ||
{ | ||
"detail": str(e), # E.g. "[errno -42] <some error message>" | ||
"component": "rbd", # this can be null to represent a global error code | ||
"code": "3", # Or a error name, e.g. "code": "some_error_key" | ||
'status': 3, # Indicating the @ViewCache error status | ||
} | ||
|
||
In case, the API Endpoints uses a task the error looks like so:: | ||
|
||
Status Code 400 | ||
{ | ||
"detail": str(e), # E.g. "[errno -42] <some error message>" | ||
"component": "rbd", # this can be null to represent a global error code | ||
"code": "3", # Or a error name, e.g. "code": "some_error_key" | ||
"task": { # Information about the task itself | ||
"name": "taskname", | ||
"metadata": {...} | ||
} | ||
} | ||
|
||
|
||
Our WebUI should show errors generated by the API to the user. Especially | ||
field-related errors in wizards and dialogs or show non-intrusive notifications. | ||
|
||
Handling exceptions in Python should be an exception. In general, we | ||
should have few exception handlers in our project. Per default, propagate | ||
errors to the API, as it will take care of all exceptions anyway. In general, | ||
log the exception by adding ``logger.exception()`` with a description to the | ||
handler. | ||
|
||
We need to distinguish between user errors from internal errors and | ||
programming errors. Using different exception types will ease the | ||
task for the API layer and for the user interface: | ||
|
||
Standard Python errors, like ``SystemError``, ``ValueError`` or ``KeyError`` | ||
will end up as internal server errors in the API. | ||
|
||
In general, do not ``return`` error responses in the REST API. They will be | ||
returned by the error handler. Instead, raise the appropriate exception. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why are you changing this part? did you find any bug while using a thread to poll for the task to finish?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This waiter-thread obscured the function and thus made debugging of error codes more complex. As the thread is not needed, I removed it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But the new code changed a bit the semantics. While before the timeout value really represents the maximum time we will wait for the task to finish, with the new code, you may wait for much more time because you need to take into account the latency of each HTTP request.
Moreover, this is code that will be run in teuthology, and if you were having problems with debug information, you should instead improve the logging information of existing code in order for everyone to get better information when investigating testing failures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. We shouldn't keep code that is not really necessary. Especially threads add lots of complexity. We should keep all code clean: Production code and QA code.