REST server work in progress
Supports:
list, enumerate, attr, instance GET operations
method, instance POST
attr, instance PUT
diff --git a/phosphor-rest b/phosphor-rest
new file mode 100644
index 0000000..600b3d5
--- /dev/null
+++ b/phosphor-rest
@@ -0,0 +1,342 @@
+#!/usr/bin/env python
+
+# Contributors Listed Below - COPYRIGHT 2015
+# [+] International Business Machines Corp.
+#
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# permissions and limitations under the License.
+
+import BaseHTTPServer
+import SocketServer
+import json
+import dbus
+from OpenBMCMapper import Path
+import OpenBMCMapper
+
+class RestException(Exception):
+ def __init__(self, msg, http_status=403):
+ self.status = http_status
+ super(RestException, self).__init__(msg)
+
+class Response(object):
+ def render(self, handler):
+ raise NotImplemented()
+
+class ErrorResponse(Response):
+ def __init__(self, ex):
+ self.ex = ex
+
+ def render(self, handler):
+ err = {'status': 'error', 'error': self.ex.message,}
+ handler.send_response(self.ex.status)
+ handler.send_header('Content-Type', 'application/json')
+ handler.end_headers()
+ handler.wfile.write(json.dumps(err, indent=2, sort_keys=True))
+
+class JSONResponse(Response):
+ def __init__(self, data):
+ self.data = data
+
+ def render(self, handler):
+ handler.send_response(200)
+ handler.send_header('Content-Type', 'application/json')
+ handler.end_headers()
+ handler.wfile.write(json.dumps(self.data, indent=2, sort_keys=True))
+
+class RequestHandler(object):
+ def __init__(self, req, path, data):
+ self.req = req
+ self.path = path
+ self.bus = req.server.bus
+ self.mapper = req.server.mapper
+ self.data = data
+
+ def do_command(self):
+ f = getattr(self, 'do_' + self.req.command)
+ return f()
+
+ def do_GET(self):
+ raise RestException("Not Implemented", 501)
+
+ def do_PUT(self):
+ raise RestException("Not Implemented", 501)
+
+ def do_POST(self):
+ raise RestException("Not Implemented", 501)
+
+ def do_PATCH(self):
+ raise RestException("Not Implemented", 501)
+
+ def do_DELETE(self):
+ raise RestException("Not Implemented", 501)
+
+class MethodHandler(RequestHandler):
+ def __init__(self, req, path, data):
+ super(MethodHandler, self).__init__(req, path, data)
+ self.method = Path(self.req.path).rel(first = -1)
+
+ def find_method_in_interface(self, obj, interface):
+ try:
+ iface = dbus.Interface(obj, interface)
+ return getattr(iface, self.method)
+ except dbus.DBusException:
+ return None
+
+ def find_method_on_bus(self, bus, interfaces):
+ obj = self.bus.get_object(bus, self.path)
+ for i in interfaces:
+ m = self.find_method_in_interface(obj, i)
+ if not m:
+ continue
+ return m
+
+ def find_method(self):
+ busses = self.mapper.GetTree(
+ self.path, 0, 'exact')[self.path]
+ for items in busses.iteritems():
+ m = self.find_method_on_bus(*items)
+ if not m:
+ continue
+
+ return m
+
+ def do_POST(self):
+ try:
+ method = self.find_method()
+ except:
+ raise RestException("Not Found", 404)
+ try:
+ d = { 'result': method(*self.data),
+ 'status': 'OK'}
+ except Exception, e:
+ d = { 'error': str(e),
+ 'status': 'error'}
+ return d
+
+class InstanceHandler(RequestHandler):
+ def __init__(self, req, path, data, busses):
+ super(InstanceHandler, self).__init__(req, path, data)
+ self.busses = busses
+
+ def get_one_iface(self, properties_iface, iface):
+ try:
+ return properties_iface.GetAll(iface)
+ except:
+ # interface doesn't have any properties
+ return {}
+
+ def get_one_bus(self, bus, interfaces):
+ properties = {}
+ obj = self.bus.get_object(bus, self.path)
+ properties_iface = dbus.Interface(
+ obj, dbus_interface=dbus.PROPERTIES_IFACE)
+ for i in interfaces:
+ properties.update(self.get_one_iface(properties_iface, i))
+
+ return properties
+
+ def do_GET(self):
+ properties = {}
+ for item in self.busses.iteritems():
+ properties.update(self.get_one_bus(*item))
+
+ return properties
+
+ def try_set_one_interface(self, prop, value, properties_iface, interface):
+ try:
+ properties_iface.Set(interface, prop, value)
+ return True
+ except:
+ # property doesn't live on this interface/bus
+ return False
+
+ def try_set_one_bus(self, prop, value, bus, interfaces):
+ obj = self.bus.get_object(bus, self.path)
+ properties_iface = dbus.Interface(
+ obj, dbus_interface=dbus.PROPERTIES_IFACE)
+
+ for iface in interfaces:
+ if self.try_set_one_interface(prop, value,
+ properties_iface, iface):
+ return True
+
+ return False
+
+ def set_one_property(self, prop, value):
+ for item in self.busses.iteritems():
+ if not self.try_set_one_bus(prop, value, *item):
+ raise RestException("Not Found", 404)
+
+ def validate_json(self):
+ if type(self.data) != dict:
+ raise RestException("Bad Request", 400)
+
+ obj = self.do_GET()
+ if len(self.data) != len(obj):
+ raise RestException("Bad Request", 400)
+ for x in obj.iterkeys():
+ if x not in self.data:
+ raise RestException("Bad Request", 400)
+
+ def do_PUT(self):
+ try:
+ self.validate_json()
+ for p in self.data.iteritems():
+ self.set_one_property(*p)
+
+ d = { 'status': 'OK'}
+ except Exception, e:
+ d = { 'error': str(e),
+ 'status': 'error'}
+ return d
+
+ def do_POST(self):
+ for p in self.data.iteritems():
+ self.set_one_property(*p)
+
+class AttrHandler(RequestHandler):
+ def __init__(self, req, path, data):
+ super(AttrHandler, self).__init__(req, path, data)
+ try:
+ self.inst = InstanceHandler(req, path, data,
+ self.mapper.GetTree(path, 0, 'exact')[path])
+ except KeyError:
+ raise RestException("Not Found", 404)
+ self.attr = Path(self.req.path).rel(first = -1)
+
+ def do_GET(self):
+ obj = self.inst.do_GET()
+ try:
+ return obj[self.attr]
+ except KeyError:
+ raise RestException("Not Found", 404)
+
+ def do_PUT(self):
+ self.inst.set_one_property(self.attr, self.data)
+
+class TypesHandler(RequestHandler):
+ def __init__(self, req, path, data):
+ super(TypesHandler, self).__init__(req, path, data)
+
+ def do_GET(self):
+ types = self.mapper.GetTreePaths(self.path, 1, 'exact')
+ if not types:
+ raise RestException("Not Found", 404)
+
+ return types
+
+class ListHandler(RequestHandler):
+ def __init__(self, req, path, data):
+ super(ListHandler, self).__init__(req, path, data)
+
+ def do_GET(self):
+ objs = self.mapper.GetTree(self.path, -1, 'fuzzy')
+ if self.path in objs:
+ del objs[self.path]
+ if not objs:
+ raise RestException("Not Found", 404)
+
+ return objs.keys()
+
+class EnumerateHandler(RequestHandler):
+ def __init__(self, req, path, data):
+ super(EnumerateHandler, self).__init__(req, path, data)
+
+ def do_GET(self):
+ objs = {}
+ mapper_data = self.mapper.GetTree(self.path, -1, 'fuzzy')
+ if self.path in mapper_data:
+ del mapper_data[self.path]
+
+ for x,y in mapper_data.iteritems():
+ objs[x] = InstanceHandler(self.req, x, self.data, y).do_GET()
+
+ if not objs:
+ raise RestException("Not Found", 404)
+
+ return objs
+
+class DBusRestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ def get_real_handler(self, data):
+ path = Path(self.path)
+
+ if self.path[-1] == '/':
+ return TypesHandler(self, path.fq(), data)
+
+ if path.parts[-1] == 'list':
+ return ListHandler(self, path.fq(last = -1), data)
+
+ if path.parts[-1] == 'enumerate':
+ return EnumerateHandler(self, path.fq(last = -1), data)
+
+ if path.parts[-2] == 'attr':
+ return AttrHandler(self, path.fq(last = -2), data)
+
+ if path.parts[-2] == 'action':
+ return MethodHandler(self, path.fq(last = -2), data)
+
+ # have to do an objectmapper query at this point
+ mapper_entry = self.server.mapper.GetTree(
+ path.fq(), 0, 'exact')
+ if mapper_entry:
+ return InstanceHandler(self, path.fq(), data,
+ mapper_entry[path.fq()])
+
+ raise RestException("Not Found", 404)
+
+ def do_command(self):
+ data = None
+ try:
+ if self.command in ['POST', 'PUT', 'PATCH']:
+ length = int(self.headers.getheader(
+ 'content-length'))
+ data = json.loads(self.rfile.read(length))
+
+ resp = self.get_real_handler(data).do_command()
+ if not resp:
+ resp = {'status': 'OK' }
+ response = JSONResponse(resp)
+ except RestException, ex:
+ response = ErrorResponse(ex)
+
+ response.render(self)
+ self.wfile.close()
+
+ def do_GET(self):
+ return self.do_command()
+
+ def do_POST(self):
+ return self.do_command()
+
+ def do_PATCH(self):
+ return self.do_command()
+
+ def do_PUT(self):
+ return self.do_command()
+
+ def do_DELETE(self):
+ return self.do_command()
+
+class HTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
+ def __init__(self, bind, handler):
+ BaseHTTPServer.HTTPServer.__init__(self, bind, handler)
+ self.bus = dbus.SystemBus()
+ mapper = self.bus.get_object(OpenBMCMapper.MAPPER_NAME,
+ OpenBMCMapper.MAPPER_PATH)
+ self.mapper = dbus.Interface(mapper,
+ dbus_interface = OpenBMCMapper.MAPPER_IFACE)
+
+if __name__ == '__main__':
+ bus = dbus.SystemBus()
+ server = HTTPServer(('', 80), DBusRestHandler)
+ server.serve_forever()
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..ed3bf6e
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,2 @@
+[install]
+install_scripts=/usr/sbin
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..2ddb129
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,5 @@
+from distutils.core import setup
+setup(name='Phosphor REST',
+ version='1.0',
+ scripts=['phosphor-rest']
+ )