blob: 7c8e21a9c6e59c66f25473d4ac63f170959d5fd7 [file] [log] [blame]
Brad Bishopaa65f6e2015-10-27 16:28:51 -04001#!/usr/bin/env python
2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05003import os
4import sys
Brad Bishopaa65f6e2015-10-27 16:28:51 -04005import dbus
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05006import dbus.exceptions
7import json
8import logging
9from xml.etree import ElementTree
10from rocket import Rocket
11from bottle import Bottle, abort, request, response, JSONPlugin, HTTPError
Brad Bishopaa65f6e2015-10-27 16:28:51 -040012import OpenBMCMapper
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050013from OpenBMCMapper import Mapper, PathTree, IntrospectionNodeParser, ListMatch
Brad Bishopaa65f6e2015-10-27 16:28:51 -040014
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050015DBUS_UNKNOWN_INTERFACE = 'org.freedesktop.UnknownInterface'
16DBUS_UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod'
17DBUS_INVALID_ARGS = 'org.freedesktop.DBus.Error.InvalidArgs'
Brad Bishopaac521c2015-11-25 09:16:35 -050018DELETE_IFACE = 'org.openbmc.Object.Delete'
Brad Bishop9ee57c42015-11-03 14:59:29 -050019
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050020_4034_msg = "The specified %s cannot be %s: '%s'"
Brad Bishopaa65f6e2015-10-27 16:28:51 -040021
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050022def find_case_insensitive(value, lst):
23 return next((x for x in lst if x.lower() == value.lower()), None)
Brad Bishopaa65f6e2015-10-27 16:28:51 -040024
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050025def makelist(data):
26 if isinstance(data, list):
27 return data
28 elif data:
29 return [data]
30 else:
31 return []
Brad Bishopaa65f6e2015-10-27 16:28:51 -040032
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050033class RouteHandler(object):
34 def __init__(self, app, bus, verbs, rules):
35 self.app = app
36 self.bus = bus
37 self.mapper = Mapper(bus)
38 self._verbs = makelist(verbs)
39 self._rules = rules
Brad Bishopaa65f6e2015-10-27 16:28:51 -040040
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050041 def _setup(self, **kw):
42 request.route_data = {}
43 if request.method in self._verbs:
44 return self.setup(**kw)
45 else:
46 self.find(**kw)
47 raise HTTPError(405, "Method not allowed.",
48 Allow=','.join(self._verbs))
Brad Bishopaa65f6e2015-10-27 16:28:51 -040049
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050050 def __call__(self, **kw):
51 return getattr(self, 'do_' + request.method.lower())(**kw)
Brad Bishopaa65f6e2015-10-27 16:28:51 -040052
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050053 def install(self):
54 self.app.route(self._rules, callback = self,
55 method = ['GET', 'PUT', 'PATCH', 'POST', 'DELETE'])
Brad Bishopaa65f6e2015-10-27 16:28:51 -040056
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050057 @staticmethod
58 def try_mapper_call(f, callback = None, **kw):
Brad Bishopaa65f6e2015-10-27 16:28:51 -040059 try:
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050060 return f(**kw)
61 except dbus.exceptions.DBusException, e:
62 if e.get_dbus_name() != OpenBMCMapper.MAPPER_NOT_FOUND:
63 raise
64 if callback is None:
65 def callback(e, **kw):
66 abort(404, str(e))
Brad Bishopaa65f6e2015-10-27 16:28:51 -040067
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050068 callback(e, **kw)
Brad Bishopaa65f6e2015-10-27 16:28:51 -040069
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050070 @staticmethod
71 def try_properties_interface(f, *a):
Brad Bishopaa65f6e2015-10-27 16:28:51 -040072 try:
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050073 return f(*a)
74 except dbus.exceptions.DBusException, e:
75 if DBUS_UNKNOWN_INTERFACE in e.get_dbus_message():
76 # interface doesn't have any properties
77 return None
78 if DBUS_UNKNOWN_METHOD == e.get_dbus_name():
79 # properties interface not implemented at all
80 return None
81 raise
Brad Bishopaa65f6e2015-10-27 16:28:51 -040082
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050083class DirectoryHandler(RouteHandler):
84 verbs = 'GET'
85 rules = '<path:path>/'
Brad Bishopaa65f6e2015-10-27 16:28:51 -040086
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050087 def __init__(self, app, bus):
88 super(DirectoryHandler, self).__init__(
89 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -040090
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050091 def find(self, path = '/'):
92 return self.try_mapper_call(
93 self.mapper.get_subtree_paths,
94 path = path, depth = 1)
Brad Bishopaa65f6e2015-10-27 16:28:51 -040095
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050096 def setup(self, path = '/'):
97 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -040098
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050099 def do_get(self, path = '/'):
100 return request.route_data['map']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400101
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500102class ListNamesHandler(RouteHandler):
103 verbs = 'GET'
104 rules = ['/list', '<path:path>/list']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400105
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500106 def __init__(self, app, bus):
107 super(ListNamesHandler, self).__init__(
108 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400109
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500110 def find(self, path = '/'):
111 return self.try_mapper_call(
112 self.mapper.get_subtree, path = path).keys()
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400113
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500114 def setup(self, path = '/'):
115 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400116
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500117 def do_get(self, path = '/'):
118 return request.route_data['map']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400119
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500120class ListHandler(RouteHandler):
121 verbs = 'GET'
122 rules = ['/enumerate', '<path:path>/enumerate']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400123
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500124 def __init__(self, app, bus):
125 super(ListHandler, self).__init__(
126 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400127
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500128 def find(self, path = '/'):
129 return self.try_mapper_call(
130 self.mapper.get_subtree, path = path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400131
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500132 def setup(self, path = '/'):
133 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400134
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500135 def do_get(self, path = '/'):
Brad Bishop936f5fe2015-11-03 15:10:11 -0500136 objs = {}
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500137 mapper_data = request.route_data['map']
Brad Bishop936f5fe2015-11-03 15:10:11 -0500138 tree = PathTree()
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400139 for x,y in mapper_data.iteritems():
Brad Bishop936f5fe2015-11-03 15:10:11 -0500140 tree[x] = y
141
142 try:
143 # Check to see if the root path implements
144 # enumerate in addition to any sub tree
145 # objects.
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500146 root = self.try_mapper_call(self.mapper.get_object,
147 path = path)
148 mapper_data[path] = root
Brad Bishop936f5fe2015-11-03 15:10:11 -0500149 except:
150 pass
151
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500152 have_enumerate = [ (x[0], self.enumerate_capable(*x)) \
153 for x in mapper_data.iteritems() \
154 if self.enumerate_capable(*x) ]
Brad Bishop936f5fe2015-11-03 15:10:11 -0500155
156 for x,y in have_enumerate:
157 objs.update(self.call_enumerate(x, y))
158 tmp = tree[x]
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500159 # remove the subtree
Brad Bishop936f5fe2015-11-03 15:10:11 -0500160 del tree[x]
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500161 # add the new leaf back since enumerate results don't
162 # include the object enumerate is being invoked on
Brad Bishop936f5fe2015-11-03 15:10:11 -0500163 tree[x] = tmp
164
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500165 # make dbus calls for any remaining objects
Brad Bishop936f5fe2015-11-03 15:10:11 -0500166 for x,y in tree.dataitems():
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500167 objs[x] = self.app.instance_handler.do_get(x)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400168
169 return objs
170
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500171 @staticmethod
172 def enumerate_capable(path, bus_data):
173 busses = []
174 for name, ifaces in bus_data.iteritems():
175 if OpenBMCMapper.ENUMERATE_IFACE in ifaces:
176 busses.append(name)
177 return busses
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400178
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500179 def call_enumerate(self, path, busses):
180 objs = {}
181 for b in busses:
182 obj = self.bus.get_object(b, path, introspect = False)
183 iface = dbus.Interface(obj, OpenBMCMapper.ENUMERATE_IFACE)
184 objs.update(iface.enumerate())
185 return objs
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400186
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500187class MethodHandler(RouteHandler):
188 verbs = 'POST'
189 rules = '<path:path>/action/<method>'
190 request_type = list
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400191
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500192 def __init__(self, app, bus):
193 super(MethodHandler, self).__init__(
194 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400195
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500196 def find(self, path, method):
197 busses = self.try_mapper_call(self.mapper.get_object,
198 path = path)
199 for items in busses.iteritems():
200 m = self.find_method_on_bus(path, method, *items)
201 if m:
202 return m
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400203
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500204 abort(404, _4034_msg %('method', 'found', method))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400205
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500206 def setup(self, path, method):
207 request.route_data['method'] = self.find(path, method)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400208
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500209 def do_post(self, path, method):
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400210 try:
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500211 if request.parameter_list:
212 return request.route_data['method'](*request.parameter_list)
213 else:
214 return request.route_data['method']()
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400215
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500216 except dbus.exceptions.DBusException, e:
217 if e.get_dbus_name() == DBUS_INVALID_ARGS:
218 abort(400, str(e))
219 raise
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400220
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500221 @staticmethod
222 def find_method_in_interface(method, obj, interface, methods):
223 if methods is None:
224 return None
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400225
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500226 method = find_case_insensitive(method, methods.keys())
227 if method is not None:
228 iface = dbus.Interface(obj, interface)
229 return iface.get_dbus_method(method)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400230
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500231 def find_method_on_bus(self, path, method, bus, interfaces):
232 obj = self.bus.get_object(bus, path, introspect = False)
233 iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE)
234 data = iface.Introspect()
235 parser = IntrospectionNodeParser(
236 ElementTree.fromstring(data),
237 intf_match = ListMatch(interfaces))
238 for x,y in parser.get_interfaces().iteritems():
239 m = self.find_method_in_interface(method, obj, x,
240 y.get('method'))
241 if m:
242 return m
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400243
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500244class PropertyHandler(RouteHandler):
245 verbs = ['PUT', 'GET']
246 rules = '<path:path>/attr/<prop>'
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400247
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500248 def __init__(self, app, bus):
249 super(PropertyHandler, self).__init__(
250 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400251
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500252 def find(self, path, prop):
253 self.app.instance_handler.setup(path)
254 obj = self.app.instance_handler.do_get(path)
255 try:
256 obj[prop]
257 except KeyError, e:
258 if request.method == 'PUT':
259 abort(403, _4034_msg %('property', 'created', str(e)))
260 else:
261 abort(404, _4034_msg %('property', 'found', str(e)))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400262
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500263 return { path: obj }
264
265 def setup(self, path, prop):
266 request.route_data['obj'] = self.find(path, prop)
267
268 def do_get(self, path, prop):
269 return request.route_data['obj'][path][prop]
270
271 def do_put(self, path, prop, value = None):
272 if value is None:
273 value = request.parameter_list
274
275 prop, iface, properties_iface = self.get_host_interface(
276 path, prop, request.route_data['map'][path])
277 try:
278 properties_iface.Set(iface, prop, value)
279 except ValueError, e:
280 abort(400, str(e))
281 except dbus.exceptions.DBusException, e:
282 if e.get_dbus_name() == DBUS_INVALID_ARGS:
283 abort(403, str(e))
284 raise
285
286 def get_host_interface(self, path, prop, bus_info):
287 for bus, interfaces in bus_info.iteritems():
288 obj = self.bus.get_object(bus, path, introspect = True)
289 properties_iface = dbus.Interface(
290 obj, dbus_interface=dbus.PROPERTIES_IFACE)
291
292 info = self.get_host_interface_on_bus(
293 path, prop, properties_iface,
294 bus, interfaces)
295 if info is not None:
296 prop, iface = info
297 return prop, iface, properties_iface
298
299 def get_host_interface_on_bus(self, path, prop, iface, bus, interfaces):
300 for i in interfaces:
301 properties = self.try_properties_interface(iface.GetAll, i)
302 if properties is None:
303 continue
304 prop = find_case_insensitive(prop, properties.keys())
305 if prop is None:
306 continue
307 return prop, i
308
Brad Bishop2503bd62015-12-16 17:56:12 -0500309class SchemaHandler(RouteHandler):
310 verbs = ['GET']
311 rules = '<path:path>/schema'
312
313 def __init__(self, app, bus):
314 super(SchemaHandler, self).__init__(
315 app, bus, self.verbs, self.rules)
316
317 def find(self, path):
318 return self.try_mapper_call(
319 self.mapper.get_object,
320 path = path)
321
322 def setup(self, path):
323 request.route_data['map'] = self.find(path)
324
325 def do_get(self, path):
326 schema = {}
327 for x in request.route_data['map'].iterkeys():
328 obj = self.bus.get_object(
329 x, path, introspect = False)
330 iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE)
331 data = iface.Introspect()
332 parser = IntrospectionNodeParser(
333 ElementTree.fromstring(data))
334 for x,y in parser.get_interfaces().iteritems():
335 schema[x] = y
336
337 return schema
338
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500339class InstanceHandler(RouteHandler):
340 verbs = ['GET', 'PUT', 'DELETE']
341 rules = '<path:path>'
342 request_type = dict
343
344 def __init__(self, app, bus):
345 super(InstanceHandler, self).__init__(
346 app, bus, self.verbs, self.rules)
347
348 def find(self, path, callback = None):
349 return { path: self.try_mapper_call(
350 self.mapper.get_object,
351 callback,
352 path = path) }
353
354 def setup(self, path):
355 callback = None
356 if request.method == 'PUT':
357 def callback(e, **kw):
358 abort(403, _4034_msg %('resource',
359 'created', path))
360
361 if request.route_data.get('map') is None:
362 request.route_data['map'] = self.find(path, callback)
363
364 def do_get(self, path):
365 properties = {}
366 for item in request.route_data['map'][path].iteritems():
367 properties.update(self.get_properties_on_bus(
368 path, *item))
369
370 return properties
371
372 @staticmethod
373 def get_properties_on_iface(properties_iface, iface):
374 properties = InstanceHandler.try_properties_interface(
375 properties_iface.GetAll, iface)
376 if properties is None:
377 return {}
378 return properties
379
380 def get_properties_on_bus(self, path, bus, interfaces):
381 properties = {}
382 obj = self.bus.get_object(bus, path, introspect = False)
383 properties_iface = dbus.Interface(
384 obj, dbus_interface=dbus.PROPERTIES_IFACE)
385 for i in interfaces:
386 properties.update(self.get_properties_on_iface(
387 properties_iface, i))
388
389 return properties
390
391 def do_put(self, path):
392 # make sure all properties exist in the request
393 obj = set(self.do_get(path).keys())
394 req = set(request.parameter_list.keys())
395
396 diff = list(obj.difference(req))
397 if diff:
398 abort(403, _4034_msg %('resource', 'removed',
399 '%s/attr/%s' %(path, diff[0])))
400
401 diff = list(req.difference(obj))
402 if diff:
403 abort(403, _4034_msg %('resource', 'created',
404 '%s/attr/%s' %(path, diff[0])))
405
406 for p,v in request.parameter_list.iteritems():
407 self.app.property_handler.do_put(
408 path, p, v)
409
410 def do_delete(self, path):
411 for bus_info in request.route_data['map'][path].iteritems():
412 if self.bus_missing_delete(path, *bus_info):
413 abort(403, _4034_msg %('resource', 'removed',
414 path))
415
416 for bus in request.route_data['map'][path].iterkeys():
417 self.delete_on_bus(path, bus)
418
419 def bus_missing_delete(self, path, bus, interfaces):
420 return DELETE_IFACE not in interfaces
421
422 def delete_on_bus(self, path, bus):
423 obj = self.bus.get_object(bus, path, introspect = False)
424 delete_iface = dbus.Interface(
425 obj, dbus_interface = DELETE_IFACE)
426 delete_iface.Delete()
427
428class JsonApiRequestPlugin(object):
429 ''' Ensures request content satisfies the OpenBMC json api format. '''
430 name = 'json_api_request'
431 api = 2
432
433 error_str = "Expecting request format { 'data': <value> }, got '%s'"
434 type_error_str = "Unsupported Content-Type: '%s'"
435 json_type = "application/json"
436 request_methods = ['PUT', 'POST', 'PATCH']
437
438 @staticmethod
439 def content_expected():
440 return request.method in JsonApiRequestPlugin.request_methods
441
442 def validate_request(self):
443 if request.content_length > 0 and \
444 request.content_type != self.json_type:
445 abort(415, self.type_error_str %(request.content_type))
446
447 try:
448 request.parameter_list = request.json.get('data')
449 except ValueError, e:
450 abort(400, str(e))
451 except (AttributeError, KeyError, TypeError):
452 abort(400, self.error_str %(request.json))
453
454 def apply(self, callback, route):
455 verbs = getattr(route.get_undecorated_callback(),
456 '_verbs', None)
457 if verbs is None:
458 return callback
459
460 if not set(self.request_methods).intersection(verbs):
461 return callback
462
463 def wrap(*a, **kw):
464 if self.content_expected():
465 self.validate_request()
466 return callback(*a, **kw)
467
468 return wrap
469
470class JsonApiRequestTypePlugin(object):
471 ''' Ensures request content type satisfies the OpenBMC json api format. '''
472 name = 'json_api_method_request'
473 api = 2
474
475 error_str = "Expecting request format { 'data': %s }, got '%s'"
476
477 def apply(self, callback, route):
478 request_type = getattr(route.get_undecorated_callback(),
479 'request_type', None)
480 if request_type is None:
481 return callback
482
483 def validate_request():
484 if not isinstance(request.parameter_list, request_type):
485 abort(400, self.error_str %(str(request_type), request.json))
486
487 def wrap(*a, **kw):
488 if JsonApiRequestPlugin.content_expected():
489 validate_request()
490 return callback(*a, **kw)
491
492 return wrap
493
494class JsonApiResponsePlugin(object):
495 ''' Emits normal responses in the OpenBMC json api format. '''
496 name = 'json_api_response'
497 api = 2
498
499 def apply(self, callback, route):
500 def wrap(*a, **kw):
501 resp = { 'data': callback(*a, **kw) }
502 resp['status'] = 'ok'
503 resp['message'] = response.status_line
504 return resp
505 return wrap
506
507class JsonApiErrorsPlugin(object):
508 ''' Emits error responses in the OpenBMC json api format. '''
509 name = 'json_api_errors'
510 api = 2
511
512 def __init__(self, **kw):
513 self.app = None
514 self.function_type = None
515 self.original = None
516 self.json_opts = { x:y for x,y in kw.iteritems() \
517 if x in ['indent','sort_keys'] }
518
519 def setup(self, app):
520 self.app = app
521 self.function_type = type(app.default_error_handler)
522 self.original = app.default_error_handler
523 self.app.default_error_handler = self.function_type(
524 self.json_errors, app, Bottle)
525
526 def apply(self, callback, route):
527 return callback
528
529 def close(self):
530 self.app.default_error_handler = self.function_type(
531 self.original, self.app, Bottle)
532
533 def json_errors(self, res, error):
534 response_object = {'status': 'error', 'data': {} }
535 response_object['message'] = error.status_line
536 response_object['data']['description'] = str(error.body)
537 if error.status_code == 500:
538 response_object['data']['exception'] = repr(error.exception)
539 response_object['data']['traceback'] = error.traceback.splitlines()
540
541 json_response = json.dumps(response_object, **self.json_opts)
Brad Bishop9bfeec22015-11-17 09:14:50 -0500542 response.content_type = 'application/json'
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500543 return json_response
544
545class RestApp(Bottle):
546 def __init__(self, bus):
547 super(RestApp, self).__init__(autojson = False)
Brad Bishop53fd4932015-10-30 09:22:30 -0400548 self.bus = bus
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500549 self.mapper = Mapper(bus)
550
551 self.install_hooks()
552 self.install_plugins()
553 self.create_handlers()
554 self.install_handlers()
555
556 def install_plugins(self):
557 # install json api plugins
558 json_kw = {'indent': 2, 'sort_keys': True}
559 self.install(JSONPlugin(**json_kw))
560 self.install(JsonApiErrorsPlugin(**json_kw))
561 self.install(JsonApiResponsePlugin())
562 self.install(JsonApiRequestPlugin())
563 self.install(JsonApiRequestTypePlugin())
564
565 def install_hooks(self):
566 self.real_router_match = self.router.match
567 self.router.match = self.custom_router_match
568 self.add_hook('before_request', self.strip_extra_slashes)
569
570 def create_handlers(self):
571 # create route handlers
572 self.directory_handler = DirectoryHandler(self, self.bus)
573 self.list_names_handler = ListNamesHandler(self, self.bus)
574 self.list_handler = ListHandler(self, self.bus)
575 self.method_handler = MethodHandler(self, self.bus)
576 self.property_handler = PropertyHandler(self, self.bus)
Brad Bishop2503bd62015-12-16 17:56:12 -0500577 self.schema_handler = SchemaHandler(self, self.bus)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500578 self.instance_handler = InstanceHandler(self, self.bus)
579
580 def install_handlers(self):
581 self.directory_handler.install()
582 self.list_names_handler.install()
583 self.list_handler.install()
584 self.method_handler.install()
585 self.property_handler.install()
Brad Bishop2503bd62015-12-16 17:56:12 -0500586 self.schema_handler.install()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500587 # this has to come last, since it matches everything
588 self.instance_handler.install()
589
590 def custom_router_match(self, environ):
591 ''' The built-in Bottle algorithm for figuring out if a 404 or 405 is
592 needed doesn't work for us since the instance rules match everything.
593 This monkey-patch lets the route handler figure out which response is
594 needed. This could be accomplished with a hook but that would require
595 calling the router match function twice.
596 '''
597 route, args = self.real_router_match(environ)
598 if isinstance(route.callback, RouteHandler):
599 route.callback._setup(**args)
600
601 return route, args
602
603 @staticmethod
604 def strip_extra_slashes():
605 path = request.environ['PATH_INFO']
606 trailing = ("","/")[path[-1] == '/']
607 parts = filter(bool, path.split('/'))
608 request.environ['PATH_INFO'] = '/' + '/'.join(parts) + trailing
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400609
610if __name__ == '__main__':
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500611 log = logging.getLogger('Rocket.Errors')
612 log.setLevel(logging.INFO)
613 log.addHandler(logging.StreamHandler(sys.stdout))
614
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400615 bus = dbus.SystemBus()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500616 app = RestApp(bus)
617 default_cert = os.path.join(sys.prefix, 'share',
618 os.path.basename(__file__), 'cert.pem')
619
620 server = Rocket(('0.0.0.0',
621 443,
622 default_cert,
623 default_cert),
624 'wsgi', {'wsgi_app': app})
625 server.start()