]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard_v2: Added `RESTResource` Python class.
authorSebastian Wagner <sebastian.wagner@suse.com>
Wed, 24 Jan 2018 10:50:43 +0000 (11:50 +0100)
committerRicardo Dias <rdias@suse.com>
Mon, 5 Mar 2018 13:06:58 +0000 (13:06 +0000)
This is the base class for providing a RESTful interface to a resource.

Signed-off-by: Sebastian Wagner <sebastian.wagner@suse.com>
src/pybind/mgr/dashboard_v2/restresource.py [new file with mode: 0644]

diff --git a/src/pybind/mgr/dashboard_v2/restresource.py b/src/pybind/mgr/dashboard_v2/restresource.py
new file mode 100644 (file)
index 0000000..db9c89b
--- /dev/null
@@ -0,0 +1,98 @@
+import json
+
+import cherrypy
+
+
+def _takes_json(func):
+    def inner(*args, **kwargs):
+        body = cherrypy.request.body.read()
+        try:
+            data = json.loads(body.decode('utf-8'))
+        except json.JSONDecodeError as e:
+            raise cherrypy.HTTPError(400, 'Failed to decode JSON: {}'
+                                          .format(str(e)))
+        return func(data, *args, **kwargs)
+    return inner
+
+
+def _returns_json(func):
+    def inner(*args, **kwargs):
+        cherrypy.serving.response.headers['Content-Type'] = "application/json"
+        ret = func(*args, **kwargs)
+        return json.dumps(ret).encode('utf8')
+    return inner
+
+
+def json_error_page(status, message, traceback, version):
+    return json.dumps(dict(status=status, detail=message, traceback=traceback,
+                           version=version))
+
+
+class RESTResource(object):
+    """
+    Base class for providing a RESTful interface to a resource.
+
+    To use this class, simply derive a class from it and implement the methods
+    you want to support.  The list of possible methods are:
+
+    * list()
+    * bulk_set(data)
+    * create(data)
+    * bulk_delete()
+    * get(key)
+    * set(data, key)
+    * delete(key)
+
+    Test with curl:
+
+    curl -H "Content-Type: application/json" -X POST \
+         -d '{"username":"xyz","password":"xyz"}'  http://127.0.0.1:8080/foo
+    curl http://127.0.0.1:8080/foo
+    curl http://127.0.0.1:8080/foo/0
+
+    """
+
+    _cp_config = {
+                  'request.error_page': {'default': json_error_page},
+                  }
+
+    def _not_implemented(self, is_element):
+        methods = [method
+                   for ((method, _is_element), (meth, _))
+                   in self._method_mapping.items()
+                   if _is_element == is_element and hasattr(self, meth)]
+        cherrypy.response.headers["Allow"] = ",".join(methods)
+        raise cherrypy.HTTPError(405, "Method not implemented.")
+
+    _method_mapping = {
+        ('GET', False): ('list', 200),
+        ('PUT', False): ('bulk_set', 200),
+        ('PATCH', False): ('bulk_set', 200),
+        ('POST', False): ('create', 201),
+        ('DELETE', False): ('bulk_delete', 204),
+        ('GET', True): ('get', 200),
+        ('PUT', True): ('set', 200),
+        ('PATCH', True): ('set', 200),
+        ('DELETE', True): ('delete', 204),
+    }
+
+    @cherrypy.expose
+    def default(self, *vpath, **params):
+        cherrypy.config.update({'error_page.default': json_error_page})
+        is_element = len(vpath) > 0
+
+        (method_name, status_code) = self._method_mapping[
+                (cherrypy.request.method, is_element)]
+        method = getattr(self, method_name, None)
+        if not method:
+            self._not_implemented(is_element)
+
+        if cherrypy.request.method != 'DELETE':
+            method = _returns_json(method)
+
+        if cherrypy.request.method not in ['GET', 'DELETE']:
+            method = _takes_json(method)
+
+        cherrypy.response.status = status_code
+
+        return method(*vpath, **params)