]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: REST API returns 500 when no Content-Type is specified 37308/head
authorAvan Thakkar <athakkar@redhat.com>
Wed, 29 Jul 2020 17:56:04 +0000 (23:26 +0530)
committerAvan Thakkar <athakkar@redhat.com>
Tue, 22 Sep 2020 10:54:07 +0000 (16:24 +0530)
Fixes: https://tracker.ceph.com/issues/41060
Signed-off-by: Avan Thakkar <athakkar@redhat.com>
(cherry picked from commit ea031de0908a249b416617a1d9cc806c356520e4)
(cherry picked from commit a5bf74e6a6c7678bbb3689a4b52fe817418da58b)
(cherry picked from commit 9dd629ca7ea5bafe883f48d257386a8c5d32c1c7)

12 files changed:
src/pybind/mgr/dashboard/controllers/__init__.py
src/pybind/mgr/dashboard/controllers/auth.py
src/pybind/mgr/dashboard/controllers/cephfs.py
src/pybind/mgr/dashboard/controllers/docs.py
src/pybind/mgr/dashboard/controllers/host.py
src/pybind/mgr/dashboard/controllers/mgr_modules.py
src/pybind/mgr/dashboard/controllers/osd.py
src/pybind/mgr/dashboard/controllers/rbd.py
src/pybind/mgr/dashboard/controllers/rbd_mirroring.py
src/pybind/mgr/dashboard/controllers/rgw.py
src/pybind/mgr/dashboard/controllers/user.py
src/pybind/mgr/dashboard/module.py

index a72ed73ce0a4f883ba117ec18e7a7e4a28818c03..d872b8a30144e66c005f87e7d1cddcd1b442ebf2 100644 (file)
@@ -481,6 +481,7 @@ class BaseController(object):
         """
         An instance of this class represents an endpoint.
         """
+
         def __init__(self, ctrl, func):
             self.ctrl = ctrl
             self.inst = None
@@ -949,3 +950,17 @@ def UpdatePermission(func):  # noqa: N802
     """
     _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
index 68c3827b4e85e1920101a8a91bd3f5c3e4e453f4..96a809664b425abbfb69c7c38d8d0e1ac1772322 100644 (file)
@@ -4,7 +4,8 @@ from __future__ import absolute_import
 import logging
 import cherrypy
 
-from . import ApiController, RESTController
+from . import ApiController, RESTController, \
+    allow_empty_body
 from .. import mgr
 from ..exceptions import DashboardException
 from ..services.auth import AuthManager, JwtManager
@@ -47,6 +48,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()
index 499fae94612ce4a2367417637aff0b234afb0a9e..8461d943383394c0be0330a693b015a3d2ae06be 100644 (file)
@@ -8,7 +8,8 @@ import os
 import cherrypy
 import cephfs
 
-from . import ApiController, ControllerDoc, RESTController, UiApiController
+from . import ApiController, ControllerDoc, RESTController, UiApiController, \
+    allow_empty_body
 from .. import mgr
 from ..exceptions import DashboardException
 from ..security import Scope
@@ -385,6 +386,7 @@ class CephFS(RESTController):
         return path
 
     @RESTController.Resource('POST')
+    @allow_empty_body
     def mk_dirs(self, fs_id, path):
         """
         Create a directory.
@@ -395,6 +397,7 @@ class CephFS(RESTController):
         cfs.mk_dirs(path)
 
     @RESTController.Resource('POST')
+    @allow_empty_body
     def rm_dir(self, fs_id, path):
         """
         Remove a directory.
@@ -405,6 +408,7 @@ class CephFS(RESTController):
         cfs.rm_dir(path)
 
     @RESTController.Resource('POST')
+    @allow_empty_body
     def mk_snapshot(self, fs_id, path, name=None):
         """
         Create a snapshot.
@@ -420,6 +424,7 @@ class CephFS(RESTController):
         return cfs.mk_snapshot(path, name)
 
     @RESTController.Resource('POST')
+    @allow_empty_body
     def rm_snapshot(self, fs_id, path, name):
         """
         Remove a snapshot.
@@ -444,6 +449,7 @@ class CephFS(RESTController):
         return cfs.get_quotas(path)
 
     @RESTController.Resource('POST')
+    @allow_empty_body
     def set_quotas(self, fs_id, path, max_bytes=None, max_files=None):
         """
         Set the quotas of the specified path.
index 84de247010c1bf1398073ef05f1031dde750b1d5..09b646bc050726c12e69aabc73634966a6ef5cf5 100644 (file)
@@ -5,7 +5,8 @@ from typing import Any, Dict, Union
 import logging
 import cherrypy
 
-from . import Controller, BaseController, Endpoint, ENDPOINT_MAP
+from . import Controller, BaseController, Endpoint, ENDPOINT_MAP, \
+    allow_empty_body
 from .. import mgr
 
 from ..tools import str_to_bool
@@ -449,5 +450,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)
index db498c8aaad8983eeae721bb5a5afb97ce997a5d..c8a4d9007231e1f8276bb84b2f3f74b3c1da1d95 100644 (file)
@@ -10,7 +10,7 @@ import cherrypy
 from mgr_util import merge_dicts
 from orchestrator import HostSpec
 from . import ApiController, RESTController, Task, Endpoint, ReadPermission, \
-    UiApiController, BaseController
+    UiApiController, BaseController, allow_empty_body
 from .orchestrator import raise_if_no_orchestrator
 from .. import mgr
 from ..exceptions import DashboardException
@@ -121,10 +121,12 @@ class Host(RESTController):
         orch_client = OrchClient.instance()
         self._check_orchestrator_host_op(orch_client, hostname, True)
         orch_client.hosts.add(hostname)
+    create._cp_config = {'tools.json_in.force': False}  # pylint: disable=W0212
 
     @raise_if_no_orchestrator
     @handle_orchestrator_error('host')
     @host_task('delete', {'hostname': '{hostname}'})
+    @allow_empty_body
     def delete(self, hostname):  # pragma: no cover - requires realtime env
         orch_client = OrchClient.instance()
         self._check_orchestrator_host_op(orch_client, hostname, False)
index 0d4cc5222560c54a9708a654212d11cd35dea42d..b558bed065301784b636fa94e2bb71832e8d078d 100644 (file)
@@ -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.
index 2731ad0ffcd179e797749c7da6872aed68b9e24c..e29a056a167fdaac854b0f7c2070dbd9892425d0 100644 (file)
@@ -7,7 +7,7 @@ import time
 from ceph.deployment.drive_group import DriveGroupSpec, DriveGroupValidationError
 from mgr_util import get_most_recent_rate
 
-from . import ApiController, RESTController, Endpoint, Task
+from . import ApiController, RESTController, Endpoint, Task, allow_empty_body
 from . import CreatePermission, ReadPermission, UpdatePermission, DeletePermission
 from .orchestrator import raise_if_no_orchestrator
 from .. import mgr
@@ -190,23 +190,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.
@@ -228,6 +233,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.
@@ -281,6 +287,7 @@ class Osd(RESTController):
             component='osd', http_status_code=400, msg='Unknown method: {}'.format(method))
 
     @RESTController.Resource('POST')
+    @allow_empty_body
     def purge(self, svc_id):
         """
         Note: osd must be marked `down` before removal.
@@ -289,6 +296,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
index ec959949381fbf64bd91f8f13c9f02a6c51276df..e4c5f0d798f5badd84df9da2c78f8bf5aaf8cb9d 100644 (file)
@@ -11,7 +11,7 @@ from datetime import datetime
 import rbd
 
 from . import ApiController, RESTController, Task, UpdatePermission, \
-    DeletePermission, CreatePermission
+    DeletePermission, CreatePermission, allow_empty_body
 from .. import mgr
 from ..exceptions import DashboardException
 from ..security import Scope
@@ -173,6 +173,7 @@ class Rbd(RESTController):
               'dest_namespace': '{dest_namespace}',
               'dest_image_name': '{dest_image_name}'}, 2.0)
     @RESTController.Resource('POST')
+    @allow_empty_body
     def copy(self, image_spec, dest_pool_name, dest_namespace, dest_image_name,
              snapshot_name=None, obj_size=None, features=None,
              stripe_unit=None, stripe_count=None, data_pool=None, configuration=None):
@@ -203,6 +204,7 @@ class Rbd(RESTController):
     @RbdTask('flatten', ['{image_spec}'], 2.0)
     @RESTController.Resource('POST')
     @UpdatePermission
+    @allow_empty_body
     def flatten(self, image_spec):
 
         def _flatten(ioctx, image):
@@ -218,6 +220,7 @@ class Rbd(RESTController):
 
     @RbdTask('trash/move', ['{image_spec}'], 2.0)
     @RESTController.Resource('POST')
+    @allow_empty_body
     def move_trash(self, image_spec, delay=0):
         """Move an image to the trash.
         Images, even ones actively in-use by clones,
@@ -271,6 +274,7 @@ class RbdSnapshot(RESTController):
              ['{image_spec}', '{snapshot_name}'], 5.0)
     @RESTController.Resource('POST')
     @UpdatePermission
+    @allow_empty_body
     def rollback(self, image_spec, snapshot_name):
         def _rollback(ioctx, img, snapshot_name):
             img.rollback_to_snap(snapshot_name)
@@ -284,6 +288,7 @@ class RbdSnapshot(RESTController):
               'child_namespace': '{child_namespace}',
               'child_image_name': '{child_image_name}'}, 2.0)
     @RESTController.Resource('POST')
+    @allow_empty_body
     def clone(self, image_spec, snapshot_name, child_pool_name,
               child_image_name, child_namespace=None, obj_size=None, features=None,
               stripe_unit=None, stripe_count=None, data_pool=None, configuration=None):
@@ -364,6 +369,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())
@@ -380,6 +386,7 @@ class RbdTrash(RESTController):
     @RbdTask('trash/restore', ['{image_id_spec}', '{new_image_name}'], 2.0)
     @RESTController.Resource('POST')
     @CreatePermission
+    @allow_empty_body
     def restore(self, image_id_spec, new_image_name):
         """Restore an image from trash."""
         pool_name, namespace, image_id = parse_image_spec(image_id_spec)
index 027289030854d8a1cc8cd43214f1bd682ad02d8a..d6f8ab61ad7aaaca2c041860f9b5cb1e94bd49af 100644 (file)
@@ -12,7 +12,7 @@ import cherrypy
 import rbd
 
 from . import ApiController, Endpoint, Task, BaseController, ReadPermission, \
-    UpdatePermission, RESTController
+    UpdatePermission, RESTController, allow_empty_body
 
 from .. import mgr
 from ..security import Scope
@@ -413,6 +413,7 @@ class RbdMirroringPoolBootstrap(BaseController):
     @Endpoint(method='POST', path='token')
     @handle_rbd_mirror_error()
     @UpdatePermission
+    @allow_empty_body
     def create_token(self, pool_name):
         ioctx = mgr.rados.open_ioctx(pool_name)
         token = rbd.RBD().mirror_peer_bootstrap_create(ioctx)
@@ -421,6 +422,7 @@ class RbdMirroringPoolBootstrap(BaseController):
     @Endpoint(method='POST', path='peer')
     @handle_rbd_mirror_error()
     @UpdatePermission
+    @allow_empty_body
     def import_token(self, pool_name, direction, token):
         ioctx = mgr.rados.open_ioctx(pool_name)
 
index a0dc444dcabff7d497f294c3ed1b9972352a8a8c..31fca5cfdbcfc81399497c6ed00d76a21b5d8d82 100644 (file)
@@ -6,7 +6,7 @@ import json
 
 import cherrypy
 from . import ApiController, BaseController, RESTController, Endpoint, \
-    ReadPermission
+    ReadPermission, allow_empty_body
 from ..exceptions import DashboardException
 from ..rest_client import RequestException
 from ..security import Scope, Permission
@@ -213,6 +213,7 @@ class RgwBucket(RgwRESTController):
 
         return self._append_bid(result)
 
+    @allow_empty_body
     def create(self, bucket, uid, zonegroup=None, placement_target=None,
                lock_enabled='false', lock_mode=None,
                lock_retention_period_days=None,
@@ -231,6 +232,7 @@ class RgwBucket(RgwRESTController):
         except RequestException as e:  # pragma: no cover - handling is too obvious
             raise DashboardException(e, http_status_code=500, component='rgw')
 
+    @allow_empty_body
     def set(self, bucket, bucket_id, uid, versioning_state=None,
             mfa_delete=None, mfa_token_serial=None, mfa_token_pin=None,
             lock_mode=None, lock_retention_period_days=None,
@@ -334,6 +336,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):
@@ -355,6 +358,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}
@@ -384,6 +388,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,
@@ -399,6 +404,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}
@@ -424,6 +430,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,
@@ -434,6 +441,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):
index d32ee4cc8339ae4ce3aa37568e060623ad3a1d79..35c3a05a351e83ded39cd99cfc59a3372d67db10 100644 (file)
@@ -7,7 +7,7 @@ import time
 
 import cherrypy
 
-from . import BaseController, ApiController, RESTController, Endpoint
+from . import BaseController, ApiController, RESTController, Endpoint, allow_empty_body
 from .. import mgr
 from ..exceptions import DashboardException, UserAlreadyExists, \
     UserDoesNotExist, PasswordPolicyException, PwdExpirationDateNotValid
@@ -146,6 +146,7 @@ class User(RESTController):
 class UserPasswordPolicy(RESTController):
 
     @Endpoint('POST')
+    @allow_empty_body
     def validate_password(self, password, username=None, old_password=None):
         """
         Check if the password meets the password policy.
index c0c2d8cde431a191329c9a214fcd6248cc3eb0c3..27501f963b9b1cc24ed91452e98b3cd198f6a9af 100644 (file)
@@ -140,7 +140,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,
         }