]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/dashboard: Exception handling: browsable API
authorSebastian Wagner <sebastian.wagner@suse.com>
Tue, 24 Apr 2018 16:39:56 +0000 (18:39 +0200)
committerSebastian Wagner <sebastian.wagner@suse.com>
Tue, 8 May 2018 14:49:31 +0000 (16:49 +0200)
* Added display of Exceptions
* Fixed missing sub-path
* Added delete-form
* Fixed default arguments

Signed-off-by: Sebastian Wagner <sebastian.wagner@suse.com>
src/pybind/mgr/dashboard/controllers/__init__.py
src/pybind/mgr/dashboard/controllers/rbd.py

index b0fdf02faae0297010418df4e2801a8ae839401f..b8ae52f5ea8a8addb73726ef8c8394a0c7bb4540 100644 (file)
@@ -164,28 +164,40 @@ def browsable_api_view(meth):
             return meth(self, *vpath, **kwargs)
         if 'text/html' not in cherrypy.request.headers.get('Accept', ''):
             return meth(self, *vpath, **kwargs)
+
         if '_method' in kwargs:
             cherrypy.request.method = kwargs.pop('_method').upper()
+
+        # Form typically use None as default, but HTML defaults to empty-string.
+        for k in kwargs:
+            if not kwargs[k]:
+                del kwargs[k]
+
         if '_raw' in kwargs:
             kwargs.pop('_raw')
             return meth(self, *vpath, **kwargs)
 
+        sub_path = cherrypy.request.path_info.split(self._cp_path_, 1)[-1].strip('/').split('/')
+
         template = """
         <html>
         <h1>Browsable API</h1>
         {docstring}
         <h2>Request</h2>
         <p>{method} {breadcrump}</p>
+        {params}
         <h2>Response</h2>
         <p>Status: {status_code}<p>
         <pre>{reponse_headers}</pre>
-        <form action="/api/{path}/{vpath}" method="get">
+        <form action="/api/{path}/{sub_path}" method="get">
         <input type="hidden" name="_raw" value="true" />
         <button type="submit">GET raw data</button>
         </form>
         <h2>Data</h2>
         <pre>{data}</pre>
+        {exception}
         {create_form}
+        {delete_form}
         <h2>Note</h2>
         <p>Please note that this API is not an official Ceph REST API to be
         used by third-party applications. It's primary purpose is to serve
@@ -195,33 +207,51 @@ def browsable_api_view(meth):
 
         create_form_template = """
         <h2>Create Form</h2>
-        <form action="/api/{path}/{vpath}" method="post">
+        <form action="/api/{path}/{sub_path}" method="post">
         {fields}<br>
         <input type="hidden" name="_method" value="post" />
         <button type="submit">Create</button>
         </form>
         """
 
-        try:
-            data = meth(self, *vpath, **kwargs)
-        except Exception as e:  # pylint: disable=broad-except
+        delete_form_template = """
+        <h2>Create Form</h2>
+        <form action="/api/{path}/{sub_path}" method="post">
+        <input type="hidden" name="_method" value="delete" />
+        <button type="submit">Delete</button>
+        </form>
+        """
+
+        def mk_exception(e):
             except_template = """
             <h2>Exception: {etype}: {tostr}</h2>
             <pre>{trace}</pre>
-            Params: {kwargs}
             """
             import traceback
             tb = sys.exc_info()[2]
             cherrypy.response.headers['Content-Type'] = 'text/html'
-            data = except_template.format(
+            return except_template.format(
                 etype=e.__class__.__name__,
                 tostr=str(e),
                 trace='\n'.join(traceback.format_tb(tb)),
                 kwargs=kwargs
             )
 
-        if cherrypy.response.headers['Content-Type'] == 'application/json':
-            data = json.dumps(json.loads(data), indent=2, sort_keys=True)
+        try:
+            data = meth(self, *vpath, **kwargs)
+            exception = ''
+            if cherrypy.response.headers['Content-Type'] == 'application/json':
+                try:
+                    data = json.dumps(json.loads(data), indent=2, sort_keys=True)
+                except Exception:  # pylint: disable=broad-except
+                    pass
+        except (ViewCacheNoDataException, DashboardException) as e:
+            cherrypy.response.status = getattr(e, 'status', 400)
+            data = str(serialize_dashboard_exception(e))
+            exception = mk_exception(e)
+        except Exception as e:  # pylint: disable=broad-except
+            data = ''
+            exception = mk_exception(e)
 
         try:
             create = getattr(self, 'create')
@@ -231,7 +261,7 @@ def browsable_api_view(meth):
             create_form = create_form_template.format(
                 fields='<br>'.join(input_fields),
                 path=self._cp_path_,
-                vpath='/'.join(vpath)
+                sub_path='/'.join(sub_path)
             )
         except AttributeError:
             create_form = ''
@@ -247,13 +277,18 @@ def browsable_api_view(meth):
             docstring='<pre>{}</pre>'.format(self.__doc__) if self.__doc__ else '',
             method=cherrypy.request.method,
             path=self._cp_path_,
-            vpath='/'.join(vpath),
-            breadcrump=mk_breadcrump(['api', self._cp_path_] + list(vpath)),
+            sub_path='/'.join(sub_path),
+            breadcrump=mk_breadcrump(['api', self._cp_path_] + list(sub_path)),
             status_code=cherrypy.response.status,
             reponse_headers='\n'.join(
                 '{}: {}'.format(k, v) for k, v in cherrypy.response.headers.items()),
             data=data,
-            create_form=create_form
+            exception=exception,
+            create_form=create_form,
+            delete_form=delete_form_template.format(path=self._cp_path_, sub_path='/'.join(
+                sub_path)) if sub_path else '',
+            params='<h2>Rrequest Params</h2><pre>{}</pre>'.format(
+                json.dumps(kwargs, indent=2)) if kwargs else '',
         )
 
     wrapper.exposed = True
index 557e3c6af379d7f1aa0644b974533a950e7ce1f3..666a49733c2939d835bb06b8084563e3b4ba21ad 100644 (file)
@@ -7,6 +7,8 @@ import math
 from functools import partial
 
 import cherrypy
+import six
+
 import rbd
 
 from . import ApiController, AuthRequired, RESTController, Task
@@ -75,9 +77,12 @@ def _format_features(features):
     >>> _format_features(None) is None
     True
 
-    >>> _format_features('not a list') is None
-    True
+    >>> _format_features('deep-flatten, exclusive-lock')
+    32
     """
+    if isinstance(features, six.string_types):
+        features = features.split(',')
+
     if not isinstance(features, list):
         return None
 
@@ -270,6 +275,8 @@ class Rbd(RESTController):
     def create(self, name, pool_name, size, obj_size=None, features=None,
                stripe_unit=None, stripe_count=None, data_pool=None):
 
+        size = int(size)
+
         def _create(ioctx):
             rbd_inst = rbd.RBD()