diff options
| author | Brad Bishop <bradleyb@us.ibm.com> | 2015-10-19 14:49:21 -0400 |
|---|---|---|
| committer | Brad Bishop <bradleyb@us.ibm.com> | 2015-10-27 17:08:45 -0400 |
| commit | 732c6db13c054654fdbd0ea80efbd176239aab42 (patch) | |
| tree | a1188e6f7da2d329ce8a14217260708059d64581 | |
| parent | b0308af60bbac4aa8a7fdf1d7e52f3b45ebbef5e (diff) | |
| download | phosphor-objmgr-732c6db13c054654fdbd0ea80efbd176239aab42.tar.gz phosphor-objmgr-732c6db13c054654fdbd0ea80efbd176239aab42.zip | |
ObjectManager work in progress
Supports:
initial discovery
service name ownership changes
interfaces added and removed signals
gettreepaths, gettree, gettrees methods
| -rw-r--r-- | OpenBMCMapper.py | 43 | ||||
| -rw-r--r-- | phosphor-mapper | 338 | ||||
| -rw-r--r-- | setup.cfg | 2 | ||||
| -rw-r--r-- | setup.py | 6 |
4 files changed, 389 insertions, 0 deletions
diff --git a/OpenBMCMapper.py b/OpenBMCMapper.py new file mode 100644 index 0000000..1862a46 --- /dev/null +++ b/OpenBMCMapper.py @@ -0,0 +1,43 @@ +#!/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. + +MAPPER_NAME = 'org.openbmc.objectmapper' +MAPPER_IFACE = MAPPER_NAME + '.ObjectMapper' +MAPPER_PATH = '/org/openbmc/objectmapper/objectmapper' + +class Path: + def __init__(self, path): + self.parts = filter(bool, path.split('/')) + + def rel(self, first = None, last = None): + # relative + return self.get('', first, last) + + def fq(self, first = None, last = None): + # fully qualified + return self.get('/', first, last) + + def depth(self): + return len(self.parts) + + def get(self, prefix = '/', first = None, last = None): + if not first: + first = 0 + if not last: + last = self.depth() + return prefix + '/'.join(self.parts[first:last]) diff --git a/phosphor-mapper b/phosphor-mapper new file mode 100644 index 0000000..28d2600 --- /dev/null +++ b/phosphor-mapper @@ -0,0 +1,338 @@ +#!/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 sys +import dbus +import dbus.service +import dbus.mainloop.glib +import gobject +from xml.etree import ElementTree +from OpenBMCMapper import Path +import OpenBMCMapper + +class DictionaryCache: + def __init__(self): + self.items = {'/': {} } + + def _merge_item(self, item): + path, bus, iface = item + if bus in self.items[path]: + self.items[path][bus].append(iface) + else: + self.items[path][bus] = [ iface ] + + def add_item(self, item): + path, bus, iface = item + if path != '/': + parent = Path(path).fq(last = -1) + if parent not in self.items: + self.add_item((parent, None, None)) + + items = self.items.get(path, None) + if bus and items: + self._merge_item(item) + elif items: + pass + elif bus: + self.items[path] = { bus: [ iface ] } + else: + self.items[path] = {} + + def add_items(self, items): + for i in items: + self.add_item(i) + + def _has_children(self, path): + return len(self.get_subtree_paths(path, 1, 'fuzzy')) != 1 + + def _try_remove_path(self, path): + if path == '/': + return None + + if not self._has_children(path): + del self.items[path] + + # try and remove the parent + parent = Path(path).fq(last = -1) + if not self.items[parent]: + self._try_remove_path(parent) + + def _remove_bus(self, path, bus): + del self.items[path][bus] + if not self.items[path]: + self._try_remove_path(path) + + def _remove_iface(self, path, bus, iface): + self.items[path][bus].remove(iface) + if not self.items[path][bus]: + self._remove_bus(path, bus) + + def remove_item(self, item): + self._remove_iface(*item) + + def remove_items(self, items): + for x in items: + self.remove_item(x) + + def remove_bus(self, name): + for path,x in self.items.items(): + for bus,ifaces in x.items(): + if name != bus: + continue + self.remove_items([ (path, bus, iface) \ + for iface in ifaces ]) + + def remove_busses(self, names): + for x in names: + self.remove_bus(name) + + def _get_busses(self): + busses = [ y.iterkeys() for y in [ x for x in self.items.itervalues() ] ] + return set( x for y in busses for x in y ) # flatten nested lists + + def has_bus(self, bus): + return any(bus in b for b in self._get_busses()) + + def get_subtree_paths(self, root, depth, match_type): + return filter(PathMatch(root, depth, match_type), + self.items.iterkeys()) + + def get_subtree(self, root, depth, match_type): + return dict(filter(ObjectMatch(root, depth, match_type), + self.items.iteritems())) + + @staticmethod + def _iface__str__(ifaces): + return '\n'.join([" %s" %(x) for x in ifaces ]) + + @staticmethod + def _bus__str__(busses): + return '\n'.join([ "%s\n%s" %(x, DictionaryCache._iface__str__(y)) \ + for x,y in busses.iteritems() ]) + + def __str__(self): + return '\n'.join([ "%s\n%s" %(x, DictionaryCache._bus__str__(y)) \ + for x,y in self.items.iteritems() ]) + +class PathMatch(object): + def __init__(self, path, depth, match_type): + p = Path(path) + self.path = p.fq() + self.depth = p.depth() + self.match_type = match_type + self.match_depth = None + if depth != -1: + self.match_depth = p.depth() + depth + + def __call__(self, path): + p = Path(path) + depth = p.depth() + + # 'is not None' is really needed because + # the match depth can be zero + if self.match_depth is not None and depth > self.match_depth: + return False + + fuzzy_match = p.fq(last = self.depth) == self.path or \ + not self.depth + depth_match = self.match_depth == depth + + if self.match_type == 'fuzzy': + return fuzzy_match + + return fuzzy_match and depth_match + +class ObjectMatch(PathMatch): + def __init__(self, path, depth, match_type): + super(ObjectMatch, self).__init__(path, depth, match_type) + + def __call__(self, tup): + if not super(ObjectMatch, self).__call__(tup[0]): + return False + return tup[1] + +class ObjectMapper(dbus.service.Object): + def __init__(self, bus, path, + name_match = 'org.openbmc', + intf_match = 'org.openbmc'): + super(ObjectMapper, self).__init__(bus.dbus, path) + self.cache = DictionaryCache() + self.bus = bus + self.name_match = name_match + self.intf_match = intf_match + self.discovery_done = False + + gobject.idle_add(self.discover) + self.bus.dbus.add_signal_receiver(self.bus_handler, + dbus_interface = dbus.BUS_DAEMON_IFACE, + signal_name = 'NameOwnerChanged') + self.bus.dbus.add_signal_receiver(self.interfaces_added_handler, + dbus_interface = dbus.BUS_DAEMON_IFACE + '.ObjectManager', + signal_name = 'InterfaceesAdded', + sender_keyword = 'sender') + self.bus.dbus.add_signal_receiver(self.interfaces_removed_handler, + dbus_interface = dbus.BUS_DAEMON_IFACE + '.ObjectManager', + signal_name = 'InterfacesRemoved', + sender_keyword = 'sender') + + def interfaces_added_handler(self, path, iprops, **kw): + for x in iprops.iterkeys(): + if self.intf_match in x: + self.cache.add_item((path, kw['sender'], x)) + + def interfaces_removed_handler(self, path, interfaces, **kw): + for x in interfaces: + self.cache.remove_item((path, kw['sender'], x)) + + def process_new_owner(self, name): + # unique name + return self.discover([ Owner(name, self.bus) ]) + + def process_old_owner(self, name): + # unique name + self.cache.remove_bus(name) + + def bus_handler(self, service, old, new): + if not self.discovery_done or \ + self.name_match not in service: + return + + if new: + self.process_new_owner(new) + if old: + self.process_old_owner(old) + + def discover(self, owners = None): + discovery = not self.discovery_done + if not owners: + owners = self.bus.get_owners(self.name_match) + self.discovery_done = True + for o in owners: + + # this only happens when an app + # grabs more than one well known name + if self.cache.has_bus(o.name): + continue + + self.cache.add_items(o.discover(self.intf_match)) + + if discovery: + print "ObjectMapper discovery complete..." + + @dbus.service.method(OpenBMCMapper.MAPPER_IFACE, 'sis', 'as') + def GetTreePaths(self, path, depth, match_type): + return self.cache.get_subtree_paths(path, depth, match_type) + + @dbus.service.method(OpenBMCMapper.MAPPER_IFACE, 'sis', 'a{sa{sas}}') + def GetTree(self, path, depth, match_type): + return self.cache.get_subtree(path, depth, match_type) + + @dbus.service.method(OpenBMCMapper.MAPPER_IFACE, 'asis', 'a{sa{sas}}') + def GetTrees(self, paths, depth, match_type): + values = {} + for x in paths: + values.update(self.GetTree(x, depth, match_type)) + return values + +class IntrospectionParser: + def __init__(self, data): + self.data = data + self.cache = {} + + def get_interfaces(self, match): + if 'interfaces' not in self.cache.keys(): + self.cache['interfaces'] = [ x.attrib['name' ] \ + for x in self.data.findall('interface') \ + if match in x.attrib['name'] ] + return self.cache['interfaces'] + + def get_kids(self): + if 'kids' not in self.cache.keys(): + self.cache['kids'] = [ x.attrib['name' ] \ + for x in self.data.findall('node') ] + return self.cache['kids'] + + def recursive_binding(self): + return any('/' in s for s in self.get_kids()) + +class Owner: + def __init__(self, name, bus): + self.name = name + self.bus = bus + + def introspect(self, path): + try: + obj = self.bus.dbus.get_object(self.name, path) + iface = dbus.Interface(obj, 'org.freedesktop.DBus.Introspectable') + data = iface.Introspect() + except dbus.DBusException: + return None + + return IntrospectionParser(ElementTree.fromstring(data)) + + def discover(self, match, path = '/'): + parser = self.introspect(path) + if not parser: + return [] + + items = [] + for x in parser.get_interfaces(match): + items.append((path, self.name, x)) + + if path != '/': + path += '/' + + recursive = parser.recursive_binding() + for k in parser.get_kids(): + if recursive: + parser = self.introspect(path + k) + if not parser: + return [] + for x in parser.get_interfaces(match): + items.append((path + k, self.name, x)) + else: + items.extend(self.discover(match, path + k)) + + return items + +class BusWrapper: + def __init__(self, bus): + self.dbus = bus + + def get_service_names(self, match): + # these are well known names + return [ x for x in self.dbus.list_names() \ + if match in x and x != OpenBMCMapper.MAPPER_NAME ] + + def get_owner_names(self, match): + # these are unique connection names + return list( set( [ self.dbus.get_name_owner(x) \ + for x in self.get_service_names(match) ] ) ) + + def get_owners(self, match): + return [ Owner(x, self) \ + for x in self.get_owner_names(match) ] + +if __name__ == '__main__': + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + bus = dbus.SystemBus() + s = dbus.service.BusName(OpenBMCMapper.MAPPER_NAME, bus) + o = ObjectMapper(BusWrapper(bus), OpenBMCMapper.MAPPER_PATH) + loop = gobject.MainLoop() + + loop.run() 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..bfbf3fc --- /dev/null +++ b/setup.py @@ -0,0 +1,6 @@ +from distutils.core import setup +setup(name='OpenBMCMapper', + version='1.0', + py_modules=['OpenBMCMapper'], + scripts=['phosphor-mapper'] + ) |

