blob: 74b59260b8ba0258a6c0fd7ba8985ca72e17026e [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 Bishopd4578922015-12-02 11:10:36 -050018DBUS_TYPE_ERROR = 'org.freedesktop.DBus.Python.TypeError'
Brad Bishopaac521c2015-11-25 09:16:35 -050019DELETE_IFACE = 'org.openbmc.Object.Delete'
Brad Bishop9ee57c42015-11-03 14:59:29 -050020
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050021_4034_msg = "The specified %s cannot be %s: '%s'"
Brad Bishopaa65f6e2015-10-27 16:28:51 -040022
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050023def find_case_insensitive(value, lst):
24 return next((x for x in lst if x.lower() == value.lower()), None)
Brad Bishopaa65f6e2015-10-27 16:28:51 -040025
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050026def makelist(data):
27 if isinstance(data, list):
28 return data
29 elif data:
30 return [data]
31 else:
32 return []
Brad Bishopaa65f6e2015-10-27 16:28:51 -040033
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050034class RouteHandler(object):
35 def __init__(self, app, bus, verbs, rules):
36 self.app = app
37 self.bus = bus
38 self.mapper = Mapper(bus)
39 self._verbs = makelist(verbs)
40 self._rules = rules
Brad Bishopaa65f6e2015-10-27 16:28:51 -040041
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050042 def _setup(self, **kw):
43 request.route_data = {}
44 if request.method in self._verbs:
45 return self.setup(**kw)
46 else:
47 self.find(**kw)
48 raise HTTPError(405, "Method not allowed.",
49 Allow=','.join(self._verbs))
Brad Bishopaa65f6e2015-10-27 16:28:51 -040050
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050051 def __call__(self, **kw):
52 return getattr(self, 'do_' + request.method.lower())(**kw)
Brad Bishopaa65f6e2015-10-27 16:28:51 -040053
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050054 def install(self):
55 self.app.route(self._rules, callback = self,
56 method = ['GET', 'PUT', 'PATCH', 'POST', 'DELETE'])
Brad Bishopaa65f6e2015-10-27 16:28:51 -040057
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050058 @staticmethod
59 def try_mapper_call(f, callback = None, **kw):
Brad Bishopaa65f6e2015-10-27 16:28:51 -040060 try:
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050061 return f(**kw)
62 except dbus.exceptions.DBusException, e:
63 if e.get_dbus_name() != OpenBMCMapper.MAPPER_NOT_FOUND:
64 raise
65 if callback is None:
66 def callback(e, **kw):
67 abort(404, str(e))
Brad Bishopaa65f6e2015-10-27 16:28:51 -040068
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050069 callback(e, **kw)
Brad Bishopaa65f6e2015-10-27 16:28:51 -040070
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050071 @staticmethod
72 def try_properties_interface(f, *a):
Brad Bishopaa65f6e2015-10-27 16:28:51 -040073 try:
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050074 return f(*a)
75 except dbus.exceptions.DBusException, e:
76 if DBUS_UNKNOWN_INTERFACE in e.get_dbus_message():
77 # interface doesn't have any properties
78 return None
79 if DBUS_UNKNOWN_METHOD == e.get_dbus_name():
80 # properties interface not implemented at all
81 return None
82 raise
Brad Bishopaa65f6e2015-10-27 16:28:51 -040083
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050084class DirectoryHandler(RouteHandler):
85 verbs = 'GET'
86 rules = '<path:path>/'
Brad Bishopaa65f6e2015-10-27 16:28:51 -040087
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050088 def __init__(self, app, bus):
89 super(DirectoryHandler, self).__init__(
90 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -040091
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050092 def find(self, path = '/'):
93 return self.try_mapper_call(
94 self.mapper.get_subtree_paths,
95 path = path, depth = 1)
Brad Bishopaa65f6e2015-10-27 16:28:51 -040096
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050097 def setup(self, path = '/'):
98 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -040099
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500100 def do_get(self, path = '/'):
101 return request.route_data['map']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400102
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500103class ListNamesHandler(RouteHandler):
104 verbs = 'GET'
105 rules = ['/list', '<path:path>/list']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400106
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500107 def __init__(self, app, bus):
108 super(ListNamesHandler, self).__init__(
109 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400110
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500111 def find(self, path = '/'):
112 return self.try_mapper_call(
113 self.mapper.get_subtree, path = path).keys()
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400114
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500115 def setup(self, path = '/'):
116 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400117
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500118 def do_get(self, path = '/'):
119 return request.route_data['map']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400120
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500121class ListHandler(RouteHandler):
122 verbs = 'GET'
123 rules = ['/enumerate', '<path:path>/enumerate']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400124
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500125 def __init__(self, app, bus):
126 super(ListHandler, self).__init__(
127 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400128
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500129 def find(self, path = '/'):
130 return self.try_mapper_call(
131 self.mapper.get_subtree, path = path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400132
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500133 def setup(self, path = '/'):
134 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400135
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500136 def do_get(self, path = '/'):
Brad Bishop936f5fe2015-11-03 15:10:11 -0500137 objs = {}
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500138 mapper_data = request.route_data['map']
Brad Bishop936f5fe2015-11-03 15:10:11 -0500139 tree = PathTree()
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400140 for x,y in mapper_data.iteritems():
Brad Bishop936f5fe2015-11-03 15:10:11 -0500141 tree[x] = y
142
143 try:
144 # Check to see if the root path implements
145 # enumerate in addition to any sub tree
146 # objects.
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500147 root = self.try_mapper_call(self.mapper.get_object,
148 path = path)
149 mapper_data[path] = root
Brad Bishop936f5fe2015-11-03 15:10:11 -0500150 except:
151 pass
152
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500153 have_enumerate = [ (x[0], self.enumerate_capable(*x)) \
154 for x in mapper_data.iteritems() \
155 if self.enumerate_capable(*x) ]
Brad Bishop936f5fe2015-11-03 15:10:11 -0500156
157 for x,y in have_enumerate:
158 objs.update(self.call_enumerate(x, y))
159 tmp = tree[x]
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500160 # remove the subtree
Brad Bishop936f5fe2015-11-03 15:10:11 -0500161 del tree[x]
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500162 # add the new leaf back since enumerate results don't
163 # include the object enumerate is being invoked on
Brad Bishop936f5fe2015-11-03 15:10:11 -0500164 tree[x] = tmp
165
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500166 # make dbus calls for any remaining objects
Brad Bishop936f5fe2015-11-03 15:10:11 -0500167 for x,y in tree.dataitems():
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500168 objs[x] = self.app.instance_handler.do_get(x)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400169
170 return objs
171
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500172 @staticmethod
173 def enumerate_capable(path, bus_data):
174 busses = []
175 for name, ifaces in bus_data.iteritems():
176 if OpenBMCMapper.ENUMERATE_IFACE in ifaces:
177 busses.append(name)
178 return busses
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400179
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500180 def call_enumerate(self, path, busses):
181 objs = {}
182 for b in busses:
183 obj = self.bus.get_object(b, path, introspect = False)
184 iface = dbus.Interface(obj, OpenBMCMapper.ENUMERATE_IFACE)
185 objs.update(iface.enumerate())
186 return objs
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400187
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500188class MethodHandler(RouteHandler):
189 verbs = 'POST'
190 rules = '<path:path>/action/<method>'
191 request_type = list
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400192
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500193 def __init__(self, app, bus):
194 super(MethodHandler, self).__init__(
195 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400196
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500197 def find(self, path, method):
198 busses = self.try_mapper_call(self.mapper.get_object,
199 path = path)
200 for items in busses.iteritems():
201 m = self.find_method_on_bus(path, method, *items)
202 if m:
203 return m
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400204
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500205 abort(404, _4034_msg %('method', 'found', method))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400206
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500207 def setup(self, path, method):
208 request.route_data['method'] = self.find(path, method)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400209
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500210 def do_post(self, path, method):
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400211 try:
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500212 if request.parameter_list:
213 return request.route_data['method'](*request.parameter_list)
214 else:
215 return request.route_data['method']()
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400216
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500217 except dbus.exceptions.DBusException, e:
218 if e.get_dbus_name() == DBUS_INVALID_ARGS:
219 abort(400, str(e))
Brad Bishopd4578922015-12-02 11:10:36 -0500220 if e.get_dbus_name() == DBUS_TYPE_ERROR:
221 abort(400, str(e))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500222 raise
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400223
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500224 @staticmethod
225 def find_method_in_interface(method, obj, interface, methods):
226 if methods is None:
227 return None
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400228
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500229 method = find_case_insensitive(method, methods.keys())
230 if method is not None:
231 iface = dbus.Interface(obj, interface)
232 return iface.get_dbus_method(method)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400233
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500234 def find_method_on_bus(self, path, method, bus, interfaces):
235 obj = self.bus.get_object(bus, path, introspect = False)
236 iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE)
237 data = iface.Introspect()
238 parser = IntrospectionNodeParser(
239 ElementTree.fromstring(data),
240 intf_match = ListMatch(interfaces))
241 for x,y in parser.get_interfaces().iteritems():
242 m = self.find_method_in_interface(method, obj, x,
243 y.get('method'))
244 if m:
245 return m
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400246
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500247class PropertyHandler(RouteHandler):
248 verbs = ['PUT', 'GET']
249 rules = '<path:path>/attr/<prop>'
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400250
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500251 def __init__(self, app, bus):
252 super(PropertyHandler, self).__init__(
253 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400254
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500255 def find(self, path, prop):
256 self.app.instance_handler.setup(path)
257 obj = self.app.instance_handler.do_get(path)
258 try:
259 obj[prop]
260 except KeyError, e:
261 if request.method == 'PUT':
262 abort(403, _4034_msg %('property', 'created', str(e)))
263 else:
264 abort(404, _4034_msg %('property', 'found', str(e)))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400265
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500266 return { path: obj }
267
268 def setup(self, path, prop):
269 request.route_data['obj'] = self.find(path, prop)
270
271 def do_get(self, path, prop):
272 return request.route_data['obj'][path][prop]
273
274 def do_put(self, path, prop, value = None):
275 if value is None:
276 value = request.parameter_list
277
278 prop, iface, properties_iface = self.get_host_interface(
279 path, prop, request.route_data['map'][path])
280 try:
281 properties_iface.Set(iface, prop, value)
282 except ValueError, e:
283 abort(400, str(e))
284 except dbus.exceptions.DBusException, e:
285 if e.get_dbus_name() == DBUS_INVALID_ARGS:
286 abort(403, str(e))
287 raise
288
289 def get_host_interface(self, path, prop, bus_info):
290 for bus, interfaces in bus_info.iteritems():
291 obj = self.bus.get_object(bus, path, introspect = True)
292 properties_iface = dbus.Interface(
293 obj, dbus_interface=dbus.PROPERTIES_IFACE)
294
295 info = self.get_host_interface_on_bus(
296 path, prop, properties_iface,
297 bus, interfaces)
298 if info is not None:
299 prop, iface = info
300 return prop, iface, properties_iface
301
302 def get_host_interface_on_bus(self, path, prop, iface, bus, interfaces):
303 for i in interfaces:
304 properties = self.try_properties_interface(iface.GetAll, i)
305 if properties is None:
306 continue
307 prop = find_case_insensitive(prop, properties.keys())
308 if prop is None:
309 continue
310 return prop, i
311
312class InstanceHandler(RouteHandler):
313 verbs = ['GET', 'PUT', 'DELETE']
314 rules = '<path:path>'
315 request_type = dict
316
317 def __init__(self, app, bus):
318 super(InstanceHandler, self).__init__(
319 app, bus, self.verbs, self.rules)
320
321 def find(self, path, callback = None):
322 return { path: self.try_mapper_call(
323 self.mapper.get_object,
324 callback,
325 path = path) }
326
327 def setup(self, path):
328 callback = None
329 if request.method == 'PUT':
330 def callback(e, **kw):
331 abort(403, _4034_msg %('resource',
332 'created', path))
333
334 if request.route_data.get('map') is None:
335 request.route_data['map'] = self.find(path, callback)
336
337 def do_get(self, path):
338 properties = {}
339 for item in request.route_data['map'][path].iteritems():
340 properties.update(self.get_properties_on_bus(
341 path, *item))
342
343 return properties
344
345 @staticmethod
346 def get_properties_on_iface(properties_iface, iface):
347 properties = InstanceHandler.try_properties_interface(
348 properties_iface.GetAll, iface)
349 if properties is None:
350 return {}
351 return properties
352
353 def get_properties_on_bus(self, path, bus, interfaces):
354 properties = {}
355 obj = self.bus.get_object(bus, path, introspect = False)
356 properties_iface = dbus.Interface(
357 obj, dbus_interface=dbus.PROPERTIES_IFACE)
358 for i in interfaces:
359 properties.update(self.get_properties_on_iface(
360 properties_iface, i))
361
362 return properties
363
364 def do_put(self, path):
365 # make sure all properties exist in the request
366 obj = set(self.do_get(path).keys())
367 req = set(request.parameter_list.keys())
368
369 diff = list(obj.difference(req))
370 if diff:
371 abort(403, _4034_msg %('resource', 'removed',
372 '%s/attr/%s' %(path, diff[0])))
373
374 diff = list(req.difference(obj))
375 if diff:
376 abort(403, _4034_msg %('resource', 'created',
377 '%s/attr/%s' %(path, diff[0])))
378
379 for p,v in request.parameter_list.iteritems():
380 self.app.property_handler.do_put(
381 path, p, v)
382
383 def do_delete(self, path):
384 for bus_info in request.route_data['map'][path].iteritems():
385 if self.bus_missing_delete(path, *bus_info):
386 abort(403, _4034_msg %('resource', 'removed',
387 path))
388
389 for bus in request.route_data['map'][path].iterkeys():
390 self.delete_on_bus(path, bus)
391
392 def bus_missing_delete(self, path, bus, interfaces):
393 return DELETE_IFACE not in interfaces
394
395 def delete_on_bus(self, path, bus):
396 obj = self.bus.get_object(bus, path, introspect = False)
397 delete_iface = dbus.Interface(
398 obj, dbus_interface = DELETE_IFACE)
399 delete_iface.Delete()
400
401class JsonApiRequestPlugin(object):
402 ''' Ensures request content satisfies the OpenBMC json api format. '''
403 name = 'json_api_request'
404 api = 2
405
406 error_str = "Expecting request format { 'data': <value> }, got '%s'"
407 type_error_str = "Unsupported Content-Type: '%s'"
408 json_type = "application/json"
409 request_methods = ['PUT', 'POST', 'PATCH']
410
411 @staticmethod
412 def content_expected():
413 return request.method in JsonApiRequestPlugin.request_methods
414
415 def validate_request(self):
416 if request.content_length > 0 and \
417 request.content_type != self.json_type:
418 abort(415, self.type_error_str %(request.content_type))
419
420 try:
421 request.parameter_list = request.json.get('data')
422 except ValueError, e:
423 abort(400, str(e))
424 except (AttributeError, KeyError, TypeError):
425 abort(400, self.error_str %(request.json))
426
427 def apply(self, callback, route):
428 verbs = getattr(route.get_undecorated_callback(),
429 '_verbs', None)
430 if verbs is None:
431 return callback
432
433 if not set(self.request_methods).intersection(verbs):
434 return callback
435
436 def wrap(*a, **kw):
437 if self.content_expected():
438 self.validate_request()
439 return callback(*a, **kw)
440
441 return wrap
442
443class JsonApiRequestTypePlugin(object):
444 ''' Ensures request content type satisfies the OpenBMC json api format. '''
445 name = 'json_api_method_request'
446 api = 2
447
448 error_str = "Expecting request format { 'data': %s }, got '%s'"
449
450 def apply(self, callback, route):
451 request_type = getattr(route.get_undecorated_callback(),
452 'request_type', None)
453 if request_type is None:
454 return callback
455
456 def validate_request():
457 if not isinstance(request.parameter_list, request_type):
458 abort(400, self.error_str %(str(request_type), request.json))
459
460 def wrap(*a, **kw):
461 if JsonApiRequestPlugin.content_expected():
462 validate_request()
463 return callback(*a, **kw)
464
465 return wrap
466
467class JsonApiResponsePlugin(object):
468 ''' Emits normal responses in the OpenBMC json api format. '''
469 name = 'json_api_response'
470 api = 2
471
472 def apply(self, callback, route):
473 def wrap(*a, **kw):
474 resp = { 'data': callback(*a, **kw) }
475 resp['status'] = 'ok'
476 resp['message'] = response.status_line
477 return resp
478 return wrap
479
480class JsonApiErrorsPlugin(object):
481 ''' Emits error responses in the OpenBMC json api format. '''
482 name = 'json_api_errors'
483 api = 2
484
485 def __init__(self, **kw):
486 self.app = None
487 self.function_type = None
488 self.original = None
489 self.json_opts = { x:y for x,y in kw.iteritems() \
490 if x in ['indent','sort_keys'] }
491
492 def setup(self, app):
493 self.app = app
494 self.function_type = type(app.default_error_handler)
495 self.original = app.default_error_handler
496 self.app.default_error_handler = self.function_type(
497 self.json_errors, app, Bottle)
498
499 def apply(self, callback, route):
500 return callback
501
502 def close(self):
503 self.app.default_error_handler = self.function_type(
504 self.original, self.app, Bottle)
505
506 def json_errors(self, res, error):
507 response_object = {'status': 'error', 'data': {} }
508 response_object['message'] = error.status_line
509 response_object['data']['description'] = str(error.body)
510 if error.status_code == 500:
511 response_object['data']['exception'] = repr(error.exception)
512 response_object['data']['traceback'] = error.traceback.splitlines()
513
514 json_response = json.dumps(response_object, **self.json_opts)
Brad Bishop9bfeec22015-11-17 09:14:50 -0500515 response.content_type = 'application/json'
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500516 return json_response
517
518class RestApp(Bottle):
519 def __init__(self, bus):
520 super(RestApp, self).__init__(autojson = False)
Brad Bishop53fd4932015-10-30 09:22:30 -0400521 self.bus = bus
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500522 self.mapper = Mapper(bus)
523
524 self.install_hooks()
525 self.install_plugins()
526 self.create_handlers()
527 self.install_handlers()
528
529 def install_plugins(self):
530 # install json api plugins
531 json_kw = {'indent': 2, 'sort_keys': True}
532 self.install(JSONPlugin(**json_kw))
533 self.install(JsonApiErrorsPlugin(**json_kw))
534 self.install(JsonApiResponsePlugin())
535 self.install(JsonApiRequestPlugin())
536 self.install(JsonApiRequestTypePlugin())
537
538 def install_hooks(self):
539 self.real_router_match = self.router.match
540 self.router.match = self.custom_router_match
541 self.add_hook('before_request', self.strip_extra_slashes)
542
543 def create_handlers(self):
544 # create route handlers
545 self.directory_handler = DirectoryHandler(self, self.bus)
546 self.list_names_handler = ListNamesHandler(self, self.bus)
547 self.list_handler = ListHandler(self, self.bus)
548 self.method_handler = MethodHandler(self, self.bus)
549 self.property_handler = PropertyHandler(self, self.bus)
550 self.instance_handler = InstanceHandler(self, self.bus)
551
552 def install_handlers(self):
553 self.directory_handler.install()
554 self.list_names_handler.install()
555 self.list_handler.install()
556 self.method_handler.install()
557 self.property_handler.install()
558 # this has to come last, since it matches everything
559 self.instance_handler.install()
560
561 def custom_router_match(self, environ):
562 ''' The built-in Bottle algorithm for figuring out if a 404 or 405 is
563 needed doesn't work for us since the instance rules match everything.
564 This monkey-patch lets the route handler figure out which response is
565 needed. This could be accomplished with a hook but that would require
566 calling the router match function twice.
567 '''
568 route, args = self.real_router_match(environ)
569 if isinstance(route.callback, RouteHandler):
570 route.callback._setup(**args)
571
572 return route, args
573
574 @staticmethod
575 def strip_extra_slashes():
576 path = request.environ['PATH_INFO']
577 trailing = ("","/")[path[-1] == '/']
578 parts = filter(bool, path.split('/'))
579 request.environ['PATH_INFO'] = '/' + '/'.join(parts) + trailing
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400580
581if __name__ == '__main__':
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500582 log = logging.getLogger('Rocket.Errors')
583 log.setLevel(logging.INFO)
584 log.addHandler(logging.StreamHandler(sys.stdout))
585
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400586 bus = dbus.SystemBus()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500587 app = RestApp(bus)
588 default_cert = os.path.join(sys.prefix, 'share',
589 os.path.basename(__file__), 'cert.pem')
590
591 server = Rocket(('0.0.0.0',
592 443,
593 default_cert,
594 default_cert),
595 'wsgi', {'wsgi_app': app})
596 server.start()