diff options
Diffstat (limited to 'bottle-rest')
-rw-r--r-- | bottle-rest | 593 |
1 files changed, 0 insertions, 593 deletions
diff --git a/bottle-rest b/bottle-rest deleted file mode 100644 index f655599..0000000 --- a/bottle-rest +++ /dev/null @@ -1,593 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -import dbus -import dbus.exceptions -import json -import logging -from xml.etree import ElementTree -from rocket import Rocket -from bottle import Bottle, abort, request, response, JSONPlugin, HTTPError -import OpenBMCMapper -from OpenBMCMapper import Mapper, PathTree, IntrospectionNodeParser, ListMatch - -DBUS_UNKNOWN_INTERFACE = 'org.freedesktop.UnknownInterface' -DBUS_UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod' -DBUS_INVALID_ARGS = 'org.freedesktop.DBus.Error.InvalidArgs' -DELETE_IFACE = 'org.openbmc.object.Delete' - -_4034_msg = "The specified %s cannot be %s: '%s'" - -def find_case_insensitive(value, lst): - return next((x for x in lst if x.lower() == value.lower()), None) - -def makelist(data): - if isinstance(data, list): - return data - elif data: - return [data] - else: - return [] - -class RouteHandler(object): - def __init__(self, app, bus, verbs, rules): - self.app = app - self.bus = bus - self.mapper = Mapper(bus) - self._verbs = makelist(verbs) - self._rules = rules - - def _setup(self, **kw): - request.route_data = {} - if request.method in self._verbs: - return self.setup(**kw) - else: - self.find(**kw) - raise HTTPError(405, "Method not allowed.", - Allow=','.join(self._verbs)) - - def __call__(self, **kw): - return getattr(self, 'do_' + request.method.lower())(**kw) - - def install(self): - self.app.route(self._rules, callback = self, - method = ['GET', 'PUT', 'PATCH', 'POST', 'DELETE']) - - @staticmethod - def try_mapper_call(f, callback = None, **kw): - try: - return f(**kw) - except dbus.exceptions.DBusException, e: - if e.get_dbus_name() != OpenBMCMapper.MAPPER_NOT_FOUND: - raise - if callback is None: - def callback(e, **kw): - abort(404, str(e)) - - callback(e, **kw) - - @staticmethod - def try_properties_interface(f, *a): - try: - return f(*a) - except dbus.exceptions.DBusException, e: - if DBUS_UNKNOWN_INTERFACE in e.get_dbus_message(): - # interface doesn't have any properties - return None - if DBUS_UNKNOWN_METHOD == e.get_dbus_name(): - # properties interface not implemented at all - return None - raise - -class DirectoryHandler(RouteHandler): - verbs = 'GET' - rules = '<path:path>/' - - def __init__(self, app, bus): - super(DirectoryHandler, self).__init__( - app, bus, self.verbs, self.rules) - - def find(self, path = '/'): - return self.try_mapper_call( - self.mapper.get_subtree_paths, - path = path, depth = 1) - - def setup(self, path = '/'): - request.route_data['map'] = self.find(path) - - def do_get(self, path = '/'): - return request.route_data['map'] - -class ListNamesHandler(RouteHandler): - verbs = 'GET' - rules = ['/list', '<path:path>/list'] - - def __init__(self, app, bus): - super(ListNamesHandler, self).__init__( - app, bus, self.verbs, self.rules) - - def find(self, path = '/'): - return self.try_mapper_call( - self.mapper.get_subtree, path = path).keys() - - def setup(self, path = '/'): - request.route_data['map'] = self.find(path) - - def do_get(self, path = '/'): - return request.route_data['map'] - -class ListHandler(RouteHandler): - verbs = 'GET' - rules = ['/enumerate', '<path:path>/enumerate'] - - def __init__(self, app, bus): - super(ListHandler, self).__init__( - app, bus, self.verbs, self.rules) - - def find(self, path = '/'): - return self.try_mapper_call( - self.mapper.get_subtree, path = path) - - def setup(self, path = '/'): - request.route_data['map'] = self.find(path) - - def do_get(self, path = '/'): - objs = {} - mapper_data = request.route_data['map'] - tree = PathTree() - for x,y in mapper_data.iteritems(): - tree[x] = y - - try: - # Check to see if the root path implements - # enumerate in addition to any sub tree - # objects. - root = self.try_mapper_call(self.mapper.get_object, - path = path) - mapper_data[path] = root - except: - pass - - have_enumerate = [ (x[0], self.enumerate_capable(*x)) \ - for x in mapper_data.iteritems() \ - if self.enumerate_capable(*x) ] - - for x,y in have_enumerate: - objs.update(self.call_enumerate(x, y)) - tmp = tree[x] - # remove the subtree - del tree[x] - # add the new leaf back since enumerate results don't - # include the object enumerate is being invoked on - tree[x] = tmp - - # make dbus calls for any remaining objects - for x,y in tree.dataitems(): - objs[x] = self.app.instance_handler.do_get(x) - - return objs - - @staticmethod - def enumerate_capable(path, bus_data): - busses = [] - for name, ifaces in bus_data.iteritems(): - if OpenBMCMapper.ENUMERATE_IFACE in ifaces: - busses.append(name) - return busses - - def call_enumerate(self, path, busses): - objs = {} - for b in busses: - obj = self.bus.get_object(b, path, introspect = False) - iface = dbus.Interface(obj, OpenBMCMapper.ENUMERATE_IFACE) - objs.update(iface.enumerate()) - return objs - -class MethodHandler(RouteHandler): - verbs = 'POST' - rules = '<path:path>/action/<method>' - request_type = list - - def __init__(self, app, bus): - super(MethodHandler, self).__init__( - app, bus, self.verbs, self.rules) - - def find(self, path, method): - busses = self.try_mapper_call(self.mapper.get_object, - path = path) - for items in busses.iteritems(): - m = self.find_method_on_bus(path, method, *items) - if m: - return m - - abort(404, _4034_msg %('method', 'found', method)) - - def setup(self, path, method): - request.route_data['method'] = self.find(path, method) - - def do_post(self, path, method): - try: - if request.parameter_list: - return request.route_data['method'](*request.parameter_list) - else: - return request.route_data['method']() - - except dbus.exceptions.DBusException, e: - if e.get_dbus_name() == DBUS_INVALID_ARGS: - abort(400, str(e)) - raise - - @staticmethod - def find_method_in_interface(method, obj, interface, methods): - if methods is None: - return None - - method = find_case_insensitive(method, methods.keys()) - if method is not None: - iface = dbus.Interface(obj, interface) - return iface.get_dbus_method(method) - - def find_method_on_bus(self, path, method, bus, interfaces): - obj = self.bus.get_object(bus, path, introspect = False) - iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE) - data = iface.Introspect() - parser = IntrospectionNodeParser( - ElementTree.fromstring(data), - intf_match = ListMatch(interfaces)) - for x,y in parser.get_interfaces().iteritems(): - m = self.find_method_in_interface(method, obj, x, - y.get('method')) - if m: - return m - -class PropertyHandler(RouteHandler): - verbs = ['PUT', 'GET'] - rules = '<path:path>/attr/<prop>' - - def __init__(self, app, bus): - super(PropertyHandler, self).__init__( - app, bus, self.verbs, self.rules) - - def find(self, path, prop): - self.app.instance_handler.setup(path) - obj = self.app.instance_handler.do_get(path) - try: - obj[prop] - except KeyError, e: - if request.method == 'PUT': - abort(403, _4034_msg %('property', 'created', str(e))) - else: - abort(404, _4034_msg %('property', 'found', str(e))) - - return { path: obj } - - def setup(self, path, prop): - request.route_data['obj'] = self.find(path, prop) - - def do_get(self, path, prop): - return request.route_data['obj'][path][prop] - - def do_put(self, path, prop, value = None): - if value is None: - value = request.parameter_list - - prop, iface, properties_iface = self.get_host_interface( - path, prop, request.route_data['map'][path]) - try: - properties_iface.Set(iface, prop, value) - except ValueError, e: - abort(400, str(e)) - except dbus.exceptions.DBusException, e: - if e.get_dbus_name() == DBUS_INVALID_ARGS: - abort(403, str(e)) - raise - - def get_host_interface(self, path, prop, bus_info): - for bus, interfaces in bus_info.iteritems(): - obj = self.bus.get_object(bus, path, introspect = True) - properties_iface = dbus.Interface( - obj, dbus_interface=dbus.PROPERTIES_IFACE) - - info = self.get_host_interface_on_bus( - path, prop, properties_iface, - bus, interfaces) - if info is not None: - prop, iface = info - return prop, iface, properties_iface - - def get_host_interface_on_bus(self, path, prop, iface, bus, interfaces): - for i in interfaces: - properties = self.try_properties_interface(iface.GetAll, i) - if properties is None: - continue - prop = find_case_insensitive(prop, properties.keys()) - if prop is None: - continue - return prop, i - -class InstanceHandler(RouteHandler): - verbs = ['GET', 'PUT', 'DELETE'] - rules = '<path:path>' - request_type = dict - - def __init__(self, app, bus): - super(InstanceHandler, self).__init__( - app, bus, self.verbs, self.rules) - - def find(self, path, callback = None): - return { path: self.try_mapper_call( - self.mapper.get_object, - callback, - path = path) } - - def setup(self, path): - callback = None - if request.method == 'PUT': - def callback(e, **kw): - abort(403, _4034_msg %('resource', - 'created', path)) - - if request.route_data.get('map') is None: - request.route_data['map'] = self.find(path, callback) - - def do_get(self, path): - properties = {} - for item in request.route_data['map'][path].iteritems(): - properties.update(self.get_properties_on_bus( - path, *item)) - - return properties - - @staticmethod - def get_properties_on_iface(properties_iface, iface): - properties = InstanceHandler.try_properties_interface( - properties_iface.GetAll, iface) - if properties is None: - return {} - return properties - - def get_properties_on_bus(self, path, bus, interfaces): - properties = {} - obj = self.bus.get_object(bus, path, introspect = False) - properties_iface = dbus.Interface( - obj, dbus_interface=dbus.PROPERTIES_IFACE) - for i in interfaces: - properties.update(self.get_properties_on_iface( - properties_iface, i)) - - return properties - - def do_put(self, path): - # make sure all properties exist in the request - obj = set(self.do_get(path).keys()) - req = set(request.parameter_list.keys()) - - diff = list(obj.difference(req)) - if diff: - abort(403, _4034_msg %('resource', 'removed', - '%s/attr/%s' %(path, diff[0]))) - - diff = list(req.difference(obj)) - if diff: - abort(403, _4034_msg %('resource', 'created', - '%s/attr/%s' %(path, diff[0]))) - - for p,v in request.parameter_list.iteritems(): - self.app.property_handler.do_put( - path, p, v) - - def do_delete(self, path): - for bus_info in request.route_data['map'][path].iteritems(): - if self.bus_missing_delete(path, *bus_info): - abort(403, _4034_msg %('resource', 'removed', - path)) - - for bus in request.route_data['map'][path].iterkeys(): - self.delete_on_bus(path, bus) - - def bus_missing_delete(self, path, bus, interfaces): - return DELETE_IFACE not in interfaces - - def delete_on_bus(self, path, bus): - obj = self.bus.get_object(bus, path, introspect = False) - delete_iface = dbus.Interface( - obj, dbus_interface = DELETE_IFACE) - delete_iface.Delete() - -class JsonApiRequestPlugin(object): - ''' Ensures request content satisfies the OpenBMC json api format. ''' - name = 'json_api_request' - api = 2 - - error_str = "Expecting request format { 'data': <value> }, got '%s'" - type_error_str = "Unsupported Content-Type: '%s'" - json_type = "application/json" - request_methods = ['PUT', 'POST', 'PATCH'] - - @staticmethod - def content_expected(): - return request.method in JsonApiRequestPlugin.request_methods - - def validate_request(self): - if request.content_length > 0 and \ - request.content_type != self.json_type: - abort(415, self.type_error_str %(request.content_type)) - - try: - request.parameter_list = request.json.get('data') - except ValueError, e: - abort(400, str(e)) - except (AttributeError, KeyError, TypeError): - abort(400, self.error_str %(request.json)) - - def apply(self, callback, route): - verbs = getattr(route.get_undecorated_callback(), - '_verbs', None) - if verbs is None: - return callback - - if not set(self.request_methods).intersection(verbs): - return callback - - def wrap(*a, **kw): - if self.content_expected(): - self.validate_request() - return callback(*a, **kw) - - return wrap - -class JsonApiRequestTypePlugin(object): - ''' Ensures request content type satisfies the OpenBMC json api format. ''' - name = 'json_api_method_request' - api = 2 - - error_str = "Expecting request format { 'data': %s }, got '%s'" - - def apply(self, callback, route): - request_type = getattr(route.get_undecorated_callback(), - 'request_type', None) - if request_type is None: - return callback - - def validate_request(): - if not isinstance(request.parameter_list, request_type): - abort(400, self.error_str %(str(request_type), request.json)) - - def wrap(*a, **kw): - if JsonApiRequestPlugin.content_expected(): - validate_request() - return callback(*a, **kw) - - return wrap - -class JsonApiResponsePlugin(object): - ''' Emits normal responses in the OpenBMC json api format. ''' - name = 'json_api_response' - api = 2 - - def apply(self, callback, route): - def wrap(*a, **kw): - resp = { 'data': callback(*a, **kw) } - resp['status'] = 'ok' - resp['message'] = response.status_line - return resp - return wrap - -class JsonApiErrorsPlugin(object): - ''' Emits error responses in the OpenBMC json api format. ''' - name = 'json_api_errors' - api = 2 - - def __init__(self, **kw): - self.app = None - self.function_type = None - self.original = None - self.json_opts = { x:y for x,y in kw.iteritems() \ - if x in ['indent','sort_keys'] } - - def setup(self, app): - self.app = app - self.function_type = type(app.default_error_handler) - self.original = app.default_error_handler - self.app.default_error_handler = self.function_type( - self.json_errors, app, Bottle) - - def apply(self, callback, route): - return callback - - def close(self): - self.app.default_error_handler = self.function_type( - self.original, self.app, Bottle) - - def json_errors(self, res, error): - response_object = {'status': 'error', 'data': {} } - response_object['message'] = error.status_line - response_object['data']['description'] = str(error.body) - if error.status_code == 500: - response_object['data']['exception'] = repr(error.exception) - response_object['data']['traceback'] = error.traceback.splitlines() - - json_response = json.dumps(response_object, **self.json_opts) - res.content_type = 'application/json' - return json_response - -class RestApp(Bottle): - def __init__(self, bus): - super(RestApp, self).__init__(autojson = False) - self.bus = bus - self.mapper = Mapper(bus) - - self.install_hooks() - self.install_plugins() - self.create_handlers() - self.install_handlers() - - def install_plugins(self): - # install json api plugins - json_kw = {'indent': 2, 'sort_keys': True} - self.install(JSONPlugin(**json_kw)) - self.install(JsonApiErrorsPlugin(**json_kw)) - self.install(JsonApiResponsePlugin()) - self.install(JsonApiRequestPlugin()) - self.install(JsonApiRequestTypePlugin()) - - def install_hooks(self): - self.real_router_match = self.router.match - self.router.match = self.custom_router_match - self.add_hook('before_request', self.strip_extra_slashes) - - def create_handlers(self): - # create route handlers - self.directory_handler = DirectoryHandler(self, self.bus) - self.list_names_handler = ListNamesHandler(self, self.bus) - self.list_handler = ListHandler(self, self.bus) - self.method_handler = MethodHandler(self, self.bus) - self.property_handler = PropertyHandler(self, self.bus) - self.instance_handler = InstanceHandler(self, self.bus) - - def install_handlers(self): - self.directory_handler.install() - self.list_names_handler.install() - self.list_handler.install() - self.method_handler.install() - self.property_handler.install() - # this has to come last, since it matches everything - self.instance_handler.install() - - def custom_router_match(self, environ): - ''' The built-in Bottle algorithm for figuring out if a 404 or 405 is - needed doesn't work for us since the instance rules match everything. - This monkey-patch lets the route handler figure out which response is - needed. This could be accomplished with a hook but that would require - calling the router match function twice. - ''' - route, args = self.real_router_match(environ) - if isinstance(route.callback, RouteHandler): - route.callback._setup(**args) - - return route, args - - @staticmethod - def strip_extra_slashes(): - path = request.environ['PATH_INFO'] - trailing = ("","/")[path[-1] == '/'] - parts = filter(bool, path.split('/')) - request.environ['PATH_INFO'] = '/' + '/'.join(parts) + trailing - -if __name__ == '__main__': - log = logging.getLogger('Rocket.Errors') - log.setLevel(logging.INFO) - log.addHandler(logging.StreamHandler(sys.stdout)) - - bus = dbus.SystemBus() - app = RestApp(bus) - default_cert = os.path.join(sys.prefix, 'share', - os.path.basename(__file__), 'cert.pem') - - server = Rocket(('0.0.0.0', - 443, - default_cert, - default_cert), - 'wsgi', {'wsgi_app': app}) - server.start() |