Skip to content

Commit

Permalink
mgr/dashboard_v2: Renamed/moved RESTResource to RESTController
Browse files Browse the repository at this point in the history
Signed-off-by: Ricardo Dias <rdias@suse.com>
  • Loading branch information
rjfd committed Jan 25, 2018
1 parent 01742c8 commit e53f90c
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 137 deletions.
12 changes: 5 additions & 7 deletions src/pybind/mgr/dashboard_v2/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,17 +116,16 @@ Reload the dashboard plugin, and then you can access the above controller
from the web browser using the URL http://mgr_hostname:8080/api/ping2

We also provide a simple mechanism to create REST based controllers using the
``RESTResource`` class.
``RESTController`` class.

For example, we can adapt the above controller to return JSON when accessing
the endpoint with a GET request::

import cherrypy
from ..restresource import RESTResource
from ..tools import ApiController
from ..tools import ApiController, RESTController

@ApiController('ping2')
class Ping2(RESTResource):
class Ping2(RESTController):
def list(self):
return {"msg": "Hello"}

Expand All @@ -140,12 +139,11 @@ add the ``AuthRequired`` decorator to your controller class.
Example::

import cherrypy
from ..restresource import RESTResource
from ..tools import ApiController, AuthRequired
from ..tools import ApiController, AuthRequired, RESTController

@ApiController('ping2')
@AuthRequired
class Ping2(RESTResource):
class Ping2(RESTController):
def list(self):
return {"msg": "Hello"}

Expand Down
7 changes: 3 additions & 4 deletions src/pybind/mgr/dashboard_v2/controllers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
import time
import sys

from ..restresource import RESTResource
from ..tools import ApiController, AuthRequired
from ..tools import ApiController, AuthRequired, RESTController


@ApiController('auth')
class Auth(RESTResource):
class Auth(RESTController):
"""
Provide login and logout actions.
Expand All @@ -34,7 +33,7 @@ def __init__(self):
self._mod = Auth._mgr_module_
self._log = self._mod.log

@RESTResource.args_from_json
@RESTController.args_from_json
def create(self, username, password):
now = time.time()
config_username = self._mod.get_localized_config('username', None)
Expand Down
9 changes: 4 additions & 5 deletions src/pybind/mgr/dashboard_v2/controllers/ping.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@

import cherrypy

from ..restresource import RESTResource
from ..tools import ApiController, AuthRequired
from ..tools import ApiController, AuthRequired, RESTController


@ApiController('ping')
Expand All @@ -16,13 +15,13 @@ def default(self, *args):


@ApiController('echo1')
class EchoArgs(RESTResource):
@RESTResource.args_from_json
class EchoArgs(RESTController):
@RESTController.args_from_json
def create(self, msg):
return {'echo': msg}


@ApiController('echo2')
class Echo(RESTResource):
class Echo(RESTController):
def create(self, data):
return {'echo': data['msg']}
121 changes: 0 additions & 121 deletions src/pybind/mgr/dashboard_v2/restresource.py

This file was deleted.

122 changes: 122 additions & 0 deletions src/pybind/mgr/dashboard_v2/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
from __future__ import absolute_import

import importlib
import inspect
import json
import os
import pkgutil
import sys

import cherrypy


def ApiController(path):
def decorate(cls):
Expand Down Expand Up @@ -64,3 +68,121 @@ def load_controller(mgrmodule, cls):
if ctrl.__name__ == cls:
return ctrl
raise Exception("Controller class '{}' not found".format(cls))


def _json_error_page(status, message, traceback, version):
return json.dumps(dict(status=status, detail=message, traceback=traceback,
version=version))


class RESTController(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 not in ['GET', 'DELETE']:
method = RESTController._takes_json(method)

if cherrypy.request.method != 'DELETE':
method = RESTController._returns_json(method)

cherrypy.response.status = status_code

return method(*vpath, **params)

@staticmethod
def args_from_json(func):
func._args_from_json_ = True
return func

@staticmethod
def _takes_json(func):
def inner(*args, **kwargs):
content_length = int(cherrypy.request.headers['Content-Length'])
body = cherrypy.request.body.read(content_length)
if not body:
raise cherrypy.HTTPError(400, 'Empty body. Content-Length={}'
.format(content_length))
try:
data = json.loads(body.decode('utf-8'))
except Exception as e:
raise cherrypy.HTTPError(400, 'Failed to decode JSON: {}'
.format(str(e)))
if hasattr(func, '_args_from_json_'):
f_args = inspect.getargspec(func).args
n_args = []
for arg in args:
n_args.append(arg)
for arg in f_args[1:]:
if arg in data:
n_args.append(data[arg])
data.pop(arg)
kwargs.update(data)
return func(*n_args, **kwargs)
else:
return func(data, *args, **kwargs)
return inner

@staticmethod
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

0 comments on commit e53f90c

Please sign in to comment.