From dd3f42cf55d90909c0a3d87a7a10046de06cfb7a Mon Sep 17 00:00:00 2001 From: Avan Thakkar Date: Wed, 29 Jul 2020 23:26:04 +0530 Subject: [PATCH] mgr/dashboard: REST API returns 500 when no Content-Type is specified Fixes: https://tracker.ceph.com/issues/41060 Signed-off-by: Avan Thakkar (cherry picked from commit ea031de0908a249b416617a1d9cc806c356520e4) (cherry picked from commit a5bf74e6a6c7678bbb3689a4b52fe817418da58b) (cherry picked from commit 9dd629ca7ea5bafe883f48d257386a8c5d32c1c7) --- src/pybind/mgr/dashboard/controllers/__init__.py | 15 +++++++++++++++ src/pybind/mgr/dashboard/controllers/auth.py | 4 +++- src/pybind/mgr/dashboard/controllers/docs.py | 4 +++- .../mgr/dashboard/controllers/mgr_modules.py | 5 ++++- src/pybind/mgr/dashboard/controllers/osd.py | 11 ++++++++++- src/pybind/mgr/dashboard/controllers/rbd.py | 9 ++++++++- src/pybind/mgr/dashboard/controllers/rgw.py | 10 +++++++++- src/pybind/mgr/dashboard/module.py | 2 +- 8 files changed, 53 insertions(+), 7 deletions(-) diff --git a/src/pybind/mgr/dashboard/controllers/__init__.py b/src/pybind/mgr/dashboard/controllers/__init__.py index c7f75dc5b5c..0353835b1f5 100644 --- a/src/pybind/mgr/dashboard/controllers/__init__.py +++ b/src/pybind/mgr/dashboard/controllers/__init__.py @@ -477,6 +477,7 @@ class BaseController(object): """ An instance of this class represents an endpoint. """ + def __init__(self, ctrl, func): self.ctrl = ctrl self.inst = None @@ -931,3 +932,17 @@ def DeletePermission(func): def UpdatePermission(func): _set_func_permissions(func, Permission.UPDATE) return func + + +# Empty request body decorator + +def allow_empty_body(func): # noqa: N802 + """ + The POST/PUT request methods decorated with ``@allow_empty_body`` + are allowed to send empty request body. + """ + try: + func._cp_config['tools.json_in.force'] = False + except (AttributeError, KeyError): + func._cp_config = {'tools.json_in.force': False} + return func diff --git a/src/pybind/mgr/dashboard/controllers/auth.py b/src/pybind/mgr/dashboard/controllers/auth.py index f1c6545a175..ea7d6f62785 100644 --- a/src/pybind/mgr/dashboard/controllers/auth.py +++ b/src/pybind/mgr/dashboard/controllers/auth.py @@ -4,7 +4,8 @@ from __future__ import absolute_import import cherrypy import jwt -from . import ApiController, RESTController +from . import ApiController, RESTController, \ + allow_empty_body from .. import logger, mgr from ..exceptions import DashboardException from ..services.auth import AuthManager, JwtManager @@ -36,6 +37,7 @@ class Auth(RESTController): component='auth') @RESTController.Collection('POST') + @allow_empty_body def logout(self): logger.debug('Logout successful') token = JwtManager.get_token_from_header() diff --git a/src/pybind/mgr/dashboard/controllers/docs.py b/src/pybind/mgr/dashboard/controllers/docs.py index 1e487f97715..d4c33783587 100644 --- a/src/pybind/mgr/dashboard/controllers/docs.py +++ b/src/pybind/mgr/dashboard/controllers/docs.py @@ -3,7 +3,8 @@ from __future__ import absolute_import import cherrypy -from . import Controller, BaseController, Endpoint, ENDPOINT_MAP +from . import Controller, BaseController, Endpoint, ENDPOINT_MAP, \ + allow_empty_body from .. import logger, mgr from ..tools import str_to_bool @@ -444,5 +445,6 @@ class Docs(BaseController): @Endpoint('POST', path="/", json_response=False, query_params="{all_endpoints}") + @allow_empty_body def _with_token(self, token, all_endpoints=False): return self._swagger_ui_page(all_endpoints, token) diff --git a/src/pybind/mgr/dashboard/controllers/mgr_modules.py b/src/pybind/mgr/dashboard/controllers/mgr_modules.py index 6e3cb5a3ee1..c37225c9b97 100644 --- a/src/pybind/mgr/dashboard/controllers/mgr_modules.py +++ b/src/pybind/mgr/dashboard/controllers/mgr_modules.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import -from . import ApiController, RESTController +from . import ApiController, RESTController, \ + allow_empty_body from .. import mgr from ..security import Scope from ..services.ceph_service import CephService @@ -69,6 +70,7 @@ class MgrModules(RESTController): @RESTController.Resource('POST') @handle_send_command_error('mgr_modules') + @allow_empty_body def enable(self, module_name): """ Enable the specified Ceph Mgr module. @@ -81,6 +83,7 @@ class MgrModules(RESTController): @RESTController.Resource('POST') @handle_send_command_error('mgr_modules') + @allow_empty_body def disable(self, module_name): """ Disable the specified Ceph Mgr module. diff --git a/src/pybind/mgr/dashboard/controllers/osd.py b/src/pybind/mgr/dashboard/controllers/osd.py index f5606cd0339..a971a512738 100644 --- a/src/pybind/mgr/dashboard/controllers/osd.py +++ b/src/pybind/mgr/dashboard/controllers/osd.py @@ -3,7 +3,8 @@ from __future__ import absolute_import from mgr_util import get_most_recent_rate -from . import ApiController, RESTController, UpdatePermission +from . import ApiController, RESTController, UpdatePermission, \ + allow_empty_body from .. import mgr, logger from ..security import Scope from ..services.ceph_service import CephService, SendCommandError @@ -92,23 +93,28 @@ class Osd(RESTController): @RESTController.Resource('POST', query_params=['deep']) @UpdatePermission + @allow_empty_body def scrub(self, svc_id, deep=False): api_scrub = "osd deep-scrub" if str_to_bool(deep) else "osd scrub" CephService.send_command("mon", api_scrub, who=svc_id) @RESTController.Resource('POST') + @allow_empty_body def mark_out(self, svc_id): CephService.send_command('mon', 'osd out', ids=[svc_id]) @RESTController.Resource('POST') + @allow_empty_body def mark_in(self, svc_id): CephService.send_command('mon', 'osd in', ids=[svc_id]) @RESTController.Resource('POST') + @allow_empty_body def mark_down(self, svc_id): CephService.send_command('mon', 'osd down', ids=[svc_id]) @RESTController.Resource('POST') + @allow_empty_body def reweight(self, svc_id, weight): """ Reweights the OSD temporarily. @@ -130,6 +136,7 @@ class Osd(RESTController): weight=float(weight)) @RESTController.Resource('POST') + @allow_empty_body def mark_lost(self, svc_id): """ Note: osd must be marked `down` before marking lost. @@ -155,6 +162,7 @@ class Osd(RESTController): } @RESTController.Resource('POST') + @allow_empty_body def purge(self, svc_id): """ Note: osd must be marked `down` before removal. @@ -163,6 +171,7 @@ class Osd(RESTController): yes_i_really_mean_it=True) @RESTController.Resource('POST') + @allow_empty_body def destroy(self, svc_id): """ Mark osd as being destroyed. Keeps the ID intact (allowing reuse), but diff --git a/src/pybind/mgr/dashboard/controllers/rbd.py b/src/pybind/mgr/dashboard/controllers/rbd.py index 8e0b184c9b7..52dca087044 100644 --- a/src/pybind/mgr/dashboard/controllers/rbd.py +++ b/src/pybind/mgr/dashboard/controllers/rbd.py @@ -12,7 +12,7 @@ import cherrypy import rbd from . import ApiController, RESTController, Task, UpdatePermission, \ - DeletePermission, CreatePermission, ReadPermission + DeletePermission, CreatePermission, ReadPermission, allow_empty_body from .. import mgr, logger from ..security import Scope from ..services.ceph_service import CephService @@ -307,6 +307,7 @@ class Rbd(RESTController): 'dest_pool_name': '{dest_pool_name}', 'dest_image_name': '{dest_image_name}'}, 2.0) @RESTController.Resource('POST') + @allow_empty_body def copy(self, pool_name, image_name, dest_pool_name, dest_image_name, snapshot_name=None, obj_size=None, features=None, stripe_unit=None, stripe_count=None, data_pool=None, configuration=None): @@ -336,6 +337,7 @@ class Rbd(RESTController): @RbdTask('flatten', ['{pool_name}', '{image_name}'], 2.0) @RESTController.Resource('POST') @UpdatePermission + @allow_empty_body def flatten(self, pool_name, image_name): def _flatten(ioctx, image): @@ -350,6 +352,7 @@ class Rbd(RESTController): @RbdTask('trash/move', ['{pool_name}', '{image_name}'], 2.0) @RESTController.Resource('POST') + @allow_empty_body def move_trash(self, pool_name, image_name, delay=0): """Move an image to the trash. Images, even ones actively in-use by clones, @@ -408,6 +411,7 @@ class RbdSnapshot(RESTController): ['{pool_name}', '{image_name}', '{snapshot_name}'], 5.0) @RESTController.Resource('POST') @UpdatePermission + @allow_empty_body def rollback(self, pool_name, image_name, snapshot_name): def _rollback(ioctx, img, snapshot_name): img.rollback_to_snap(snapshot_name) @@ -420,6 +424,7 @@ class RbdSnapshot(RESTController): 'child_pool_name': '{child_pool_name}', 'child_image_name': '{child_image_name}'}, 2.0) @RESTController.Resource('POST') + @allow_empty_body def clone(self, pool_name, image_name, snapshot_name, child_pool_name, child_image_name, obj_size=None, features=None, stripe_unit=None, stripe_count=None, data_pool=None, configuration=None): @@ -491,6 +496,7 @@ class RbdTrash(RESTController): @RbdTask('trash/purge', ['{pool_name}'], 2.0) @RESTController.Collection('POST', query_params=['pool_name']) @DeletePermission + @allow_empty_body def purge(self, pool_name=None): """Remove all expired images from trash.""" now = "{}Z".format(datetime.utcnow().isoformat()) @@ -506,6 +512,7 @@ class RbdTrash(RESTController): @RbdTask('trash/restore', ['{pool_name}', '{image_id}', '{new_image_name}'], 2.0) @RESTController.Resource('POST') @CreatePermission + @allow_empty_body def restore(self, pool_name, image_id, new_image_name): """Restore an image from trash.""" return _rbd_call(pool_name, self.rbd_inst.trash_restore, image_id, new_image_name) diff --git a/src/pybind/mgr/dashboard/controllers/rgw.py b/src/pybind/mgr/dashboard/controllers/rgw.py index 2a53f368352..2e7014d2e10 100644 --- a/src/pybind/mgr/dashboard/controllers/rgw.py +++ b/src/pybind/mgr/dashboard/controllers/rgw.py @@ -6,7 +6,7 @@ import json import cherrypy from . import ApiController, BaseController, RESTController, Endpoint, \ - ReadPermission + ReadPermission, allow_empty_body from .. import logger from ..exceptions import DashboardException from ..rest_client import RequestException @@ -149,6 +149,7 @@ class RgwBucket(RgwRESTController): result = self.proxy('GET', 'bucket', {'bucket': bucket}) return self._append_bid(result) + @allow_empty_body def create(self, bucket, uid): try: rgw_client = RgwClient.instance(uid) @@ -156,6 +157,7 @@ class RgwBucket(RgwRESTController): except RequestException as e: raise DashboardException(e, http_status_code=500, component='rgw') + @allow_empty_body def set(self, bucket, bucket_id, uid): result = self.proxy('PUT', 'bucket', { 'bucket': RgwBucket.strip_tenant_from_bucket_name(bucket, uid), @@ -231,6 +233,7 @@ class RgwUser(RgwRESTController): emails.append(user["email"]) return emails + @allow_empty_body def create(self, uid, display_name, email=None, max_buckets=None, suspended=None, generate_key=None, access_key=None, secret_key=None): @@ -252,6 +255,7 @@ class RgwUser(RgwRESTController): result = self.proxy('PUT', 'user', params) return self._append_uid(result) + @allow_empty_body def set(self, uid, display_name=None, email=None, max_buckets=None, suspended=None): params = {'uid': uid} @@ -281,6 +285,7 @@ class RgwUser(RgwRESTController): # pylint: disable=redefined-builtin @RESTController.Resource(method='POST', path='/capability', status=201) + @allow_empty_body def create_cap(self, uid, type, perm): return self.proxy('PUT', 'user?caps', { 'uid': uid, @@ -296,6 +301,7 @@ class RgwUser(RgwRESTController): }) @RESTController.Resource(method='POST', path='/key', status=201) + @allow_empty_body def create_key(self, uid, key_type='s3', subuser=None, generate_key='true', access_key=None, secret_key=None): params = {'uid': uid, 'key-type': key_type, 'generate-key': generate_key} @@ -321,6 +327,7 @@ class RgwUser(RgwRESTController): return self.proxy('GET', 'user?quota', {'uid': uid}) @RESTController.Resource(method='PUT', path='/quota') + @allow_empty_body def set_quota(self, uid, quota_type, enabled, max_size_kb, max_objects): return self.proxy('PUT', 'user?quota', { 'uid': uid, @@ -331,6 +338,7 @@ class RgwUser(RgwRESTController): }, json_response=False) @RESTController.Resource(method='POST', path='/subuser', status=201) + @allow_empty_body def create_subuser(self, uid, subuser, access, key_type='s3', generate_secret='true', access_key=None, secret_key=None): diff --git a/src/pybind/mgr/dashboard/module.py b/src/pybind/mgr/dashboard/module.py index db7c903d165..132eea00654 100644 --- a/src/pybind/mgr/dashboard/module.py +++ b/src/pybind/mgr/dashboard/module.py @@ -143,7 +143,7 @@ class CherryPyConfig(object): 'application/javascript', ], 'tools.json_in.on': True, - 'tools.json_in.force': False, + 'tools.json_in.force': True, 'tools.plugin_hooks_filter_request.on': True, } -- 2.47.3