Merge pull request #3 from bradbishop/enumerate
Mapper refactoring
diff --git a/OpenBMCMapper.py b/OpenBMCMapper.py
index 30a93fb..1233c70 100644
--- a/OpenBMCMapper.py
+++ b/OpenBMCMapper.py
@@ -22,6 +22,11 @@
MAPPER_NAME = 'org.openbmc.objectmapper'
MAPPER_IFACE = MAPPER_NAME + '.ObjectMapper'
MAPPER_PATH = '/org/openbmc/objectmapper/objectmapper'
+ENUMERATE_IFACE = 'org.openbmc.Object.Enumerate'
+
+class MapperNotFoundException(Exception):
+ def __init__(self, msg):
+ super(MapperNotFoundException, self).__init__(msg)
class Path:
def __init__(self, path):
@@ -45,10 +50,22 @@
last = self.depth()
return prefix + '/'.join(self.parts[first:last])
+def org_dot_openbmc_match(name):
+ return 'org.openbmc' in name
+
+class ListMatch(object):
+ def __init__(self, l):
+ self.l = l
+
+ def __call__(self, match):
+ return match in self.l
+
class IntrospectionNodeParser:
- def __init__(self, data):
+ def __init__(self, data, tag_match = bool, intf_match = bool):
self.data = data
self.cache = {}
+ self.tag_match = tag_match
+ self.intf_match = intf_match
def parse_args(self):
return [ x.attrib for x in self.data.findall('arg') ]
@@ -64,16 +81,18 @@
iface = {}
iface['method'] = {}
iface['signal'] = {}
- name = self.data.attrib['name']
for node in self.data:
- p = IntrospectionNodeParser(node)
if node.tag not in ['method', 'signal']:
continue
+ if not self.tag_match(node.tag):
+ continue
+ p = IntrospectionNodeParser(
+ node, self.tag_match, self.intf_match)
n, element = p.parse_method_or_signal()
iface[node.tag][n] = element
- return name, iface
+ return iface
def parse_node(self):
if self.cache:
@@ -83,10 +102,13 @@
self.cache['children'] = []
for node in self.data:
- p = IntrospectionNodeParser(node)
if node.tag == 'interface':
- name, ifaces = p.parse_interface()
- self.cache['interfaces'][name] = ifaces
+ p = IntrospectionNodeParser(
+ node, self.tag_match, self.intf_match)
+ name = p.data.attrib['name']
+ if not self.intf_match(name):
+ continue
+ self.cache['interfaces'][name] = p.parse_interface()
elif node.tag == 'node':
self.cache['children'] = self.parse_children()
@@ -102,19 +124,24 @@
return any('/' in s for s in self.get_children())
class IntrospectionParser:
- def __init__(self, name, bus):
+ def __init__(self, name, bus, tag_match = bool, intf_match = bool):
self.name = name
self.bus = bus
+ self.tag_match = tag_match
+ self.intf_match = intf_match
def _introspect(self, path):
try:
- obj = self.bus.get_object(self.name, path)
- iface = dbus.Interface(obj, dbus.BUS_DAEMON_IFACE + '.Introspectable')
+ obj = self.bus.get_object(self.name, path, introspect = False)
+ iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE)
data = iface.Introspect()
except dbus.DBusException:
return None
- return IntrospectionNodeParser(ElementTree.fromstring(data))
+ return IntrospectionNodeParser(
+ ElementTree.fromstring(data),
+ self.tag_match,
+ self.intf_match)
def _discover_flat(self, path, parser):
items = {}
@@ -148,3 +175,184 @@
items.update(callback(path + k, parser))
return items
+
+class PathTreeItemIterator(object):
+ def __init__(self, path_tree, subtree, depth):
+ self.path_tree = path_tree
+ self.path = []
+ self.itlist = []
+ self.subtree = ['/'] + filter(bool, subtree.split('/'))
+ self.depth = depth
+ d = path_tree.root
+ for k in self.subtree:
+ try:
+ d = d[k]['children']
+ except KeyError:
+ raise KeyError(subtree)
+ self.it = d.iteritems()
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ return super(PathTreeItemIterator, self).next()
+
+ def next(self):
+ key, value = self._next()
+ path = self.subtree[0] + '/'.join(self.subtree[1:] + self.path)
+ return path, value.get('data')
+
+ def _next(self):
+ try:
+ while True:
+ x = self.it.next()
+ depth_exceeded = len(self.path) +1 > self.depth
+ if self.depth and depth_exceeded:
+ continue
+ self.itlist.append(self.it)
+ self.path.append(x[0])
+ self.it = x[1]['children'].iteritems()
+ break;
+
+ except StopIteration:
+ if not self.itlist:
+ raise StopIteration
+
+ self.it = self.itlist.pop()
+ self.path.pop()
+ x = self._next()
+
+ return x
+
+class PathTreeKeyIterator(PathTreeItemIterator):
+ def __init__(self, path_tree, subtree, depth):
+ super(PathTreeKeyIterator, self).__init__(path_tree, subtree, depth)
+
+ def next(self):
+ return super(PathTreeKeyIterator, self).next()[0]
+
+class PathTree:
+ def __init__(self):
+ self.root = {}
+
+ def _try_delete_parent(self, elements):
+ if len(elements) == 1:
+ return False
+
+ kids = 'children'
+ elements.pop()
+ d = self.root
+ for k in elements[:-1]:
+ d = d[k][kids]
+
+ if 'data' not in d[elements[-1]] and not d[elements[-1]][kids]:
+ del d[elements[-1]]
+ self._try_delete_parent(elements)
+
+ def _get_node(self, key):
+ kids = 'children'
+ elements = ['/'] + filter(bool, key.split('/'))
+ d = self.root
+ for k in elements[:-1]:
+ try:
+ d = d[k][kids]
+ except KeyError:
+ raise KeyError(key)
+
+ return d[elements[-1]]
+
+ def __iter__(self):
+ return self
+
+ def __missing__(self, key):
+ for x in self.iterkeys():
+ if key == x:
+ return False
+ return True
+
+ def __delitem__(self, key):
+ kids = 'children'
+ elements = ['/'] + filter(bool, key.split('/'))
+ d = self.root
+ for k in elements[:-1]:
+ try:
+ d = d[k][kids]
+ except KeyError:
+ raise KeyError(key)
+
+ del d[elements[-1]]
+ self._try_delete_parent(elements)
+
+ def __setitem__(self, key, value):
+ kids = 'children'
+ elements = ['/'] + filter(bool, key.split('/'))
+ d = self.root
+ for k in elements[:-1]:
+ d = d.setdefault(k, {kids: {}})[kids]
+
+ children = d.setdefault(elements[-1], {kids: {}})[kids]
+ d[elements[-1]].update({kids: children, 'data': value})
+
+ def __getitem__(self, key):
+ return self._get_node(key).get('data')
+
+ def setdefault(self, key, default):
+ if not self.get(key):
+ self.__setitem__(key, default)
+
+ return self.__getitem__(key)
+
+ def get(self, key, default = None):
+ try:
+ x = self.__getitem__(key)
+ except KeyError:
+ x = default
+
+ return x
+
+ def get_children(self, key):
+ return [ x for x in self._get_node(key)['children'].iterkeys() ]
+
+ def demote(self, key):
+ n = self._get_node(key)
+ if 'data' in n:
+ del n['data']
+
+ def keys(self, subtree = '/', depth = None):
+ return [ x for x in self.iterkeys(subtree, depth) ]
+
+ def values(self, subtree = '/', depth = None):
+ return [ x[1] for x in self.iteritems(subtree, depth) ]
+
+ def items(self, subtree = '/', depth = None):
+ return [ x for x in self.iteritems(subtree, depth) ]
+
+ def dataitems(self, subtree = '/', depth = None):
+ return [ x for x in self.iteritems(subtree, depth) \
+ if x[1] is not None ]
+
+ def iterkeys(self, subtree = '/', depth = None):
+ if not self.root:
+ return {}.iterkeys()
+ return PathTreeKeyIterator(self, subtree, depth)
+
+ def iteritems(self, subtree = '/', depth = None):
+ if not self.root:
+ return {}.iteritems()
+ return PathTreeItemIterator(self, subtree, depth)
+
+class Mapper:
+ def __init__(self, bus):
+ self.bus = bus
+ obj = bus.get_object(MAPPER_NAME, MAPPER_PATH, introspect = False)
+ self.iface = dbus.Interface(
+ obj, dbus_interface = MAPPER_IFACE)
+
+ def get_object(self, path):
+ return self.iface.GetObject(path)
+
+ def get_subtree_paths(self, path = '/', depth = 0):
+ return self.iface.GetSubTreePaths(path, depth)
+
+ def get_subtree(self, path = '/', depth = 0):
+ return self.iface.GetSubTree(path, depth)
diff --git a/phosphor-mapper b/phosphor-mapper
index 313baa2..36bf09b 100644
--- a/phosphor-mapper
+++ b/phosphor-mapper
@@ -21,160 +21,20 @@
import dbus.service
import dbus.mainloop.glib
import gobject
-from OpenBMCMapper import Path, IntrospectionParser
+from OpenBMCMapper import Path, IntrospectionParser, PathTree
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'):
+ name_match = OpenBMCMapper.org_dot_openbmc_match,
+ intf_match = OpenBMCMapper.org_dot_openbmc_match):
super(ObjectMapper, self).__init__(bus.dbus, path)
- self.cache = DictionaryCache()
+ self.cache = PathTree()
self.bus = bus
self.name_match = name_match
self.intf_match = intf_match
- self.discovery_done = False
+ self.tag_match = OpenBMCMapper.ListMatch(['children', 'interface'])
+ self.service = None
gobject.idle_add(self.discover)
self.bus.dbus.add_signal_receiver(self.bus_handler,
@@ -189,26 +49,66 @@
signal_name = 'InterfacesRemoved',
sender_keyword = 'sender')
+ def bus_match(self, name):
+ if name == OpenBMCMapper.MAPPER_NAME:
+ return False
+ return self.name_match(name)
+
+ def discovery_pending(self):
+ return not bool(self.service)
+
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))
+ if self.discovery_pending() or \
+ not self.bus_match(kw['sender']):
+ return
+ matches = [ x for x in iprops.iterkeys() if self.intf_match(x) ]
+ d = self.cache.setdefault(path, {})
+ d[path].setdefault(kw['sender'], []).extend(matches)
+ self.cache[path] = d
def interfaces_removed_handler(self, path, interfaces, **kw):
+ if self.discovery_pending() or \
+ not self.bus_match(kw['sender']):
+ return
+ item = self.cache[path]
+ name = kw['sender']
for x in interfaces:
- self.cache.remove_item((path, kw['sender'], x))
+ item[name].remove(x)
+
+ # remove the owner if there aren't any interfaces left
+ if not item[name]:
+ del item[name]
+
+ # update if interfaces remain
+ if item:
+ self.cache[path] = item
+ # mark for removal if no interfaces remain
+ elif self.cache.get_children(item):
+ self.cache.demote(item)
+ # delete the entire path if everything is gone
+ else:
+ del self.cache[path]
def process_new_owner(self, name):
# unique name
- return self.discover([ IntrospectionParser(name, self.bus.dbus) ])
+ return self.discover([ IntrospectionParser(name,
+ self.bus.dbus, self.tag_match, self.intf_match) ])
def process_old_owner(self, name):
- # unique name
- self.cache.remove_bus(name)
+ for x,y in self.cache.dataitems():
+ if name not in y:
+ continue
+ del y[name]
+ if y:
+ self.cache[x] = y
+ elif self.cache.get_children(x):
+ self.cache.demote(x)
+ else:
+ del self.cache[x]
def bus_handler(self, service, old, new):
- if not self.discovery_done or \
- self.name_match not in service:
+ if self.discovery_pending() or \
+ not self.bus_match(service):
return
if new:
@@ -216,48 +116,48 @@
if old:
self.process_old_owner(old)
- def add_match_interfaces(self, owner, path, interfaces):
- for x in interfaces:
- if self.intf_match not in x:
- continue
+ def add_interfaces(self, path, owner, interfaces):
+ d = self.cache.setdefault(path, { })
+ d.setdefault(owner, []).extend(interfaces)
+ self.cache[path] = d
- self.cache.add_item((path, owner, x))
-
- def add_match_items(self, owner, bus_items):
+ def add_items(self, owner, bus_items):
for x,y in bus_items.iteritems():
- self.add_match_interfaces(owner, x, y['interfaces'])
+ self.add_interfaces(x, owner, y['interfaces'])
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
+ owners = [ IntrospectionParser(x, self.bus.dbus,
+ self.tag_match, self.intf_match) \
+ for x in self.bus.get_owner_names(self.bus_match) ]
for o in owners:
+ self.add_items(o.name, o.introspect())
- # this only happens when an app
- # grabs more than one well known name
- if self.cache.has_bus(o.name):
- continue
-
- self.add_match_items(o.name, o.introspect())
-
- if discovery:
+ if self.discovery_pending():
print "ObjectMapper discovery complete..."
+ self.service = dbus.service.BusName(
+ OpenBMCMapper.MAPPER_NAME, self.bus.dbus)
- @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, 's', 'a{sas}')
+ def GetObject(self, path):
+ o = self.cache.get(path)
+ if not o:
+ raise MapperNotFoundException(path)
+ return o
- @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, 'si', 'as')
+ def GetSubTreePaths(self, path, depth):
+ try:
+ return self.cache.iterkeys(path, depth)
+ except KeyError:
+ raise MapperNotFoundException(path)
- @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
+ @dbus.service.method(OpenBMCMapper.MAPPER_IFACE, 'si', 'a{sa{sas}}')
+ def GetSubTree(self, path, depth):
+ try:
+ return { x:y for x, y in self.cache.dataitems(path, depth) }
+ except KeyError:
+ raise MapperNotFoundException(path)
class BusWrapper:
def __init__(self, bus):
@@ -266,21 +166,16 @@
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 ]
+ if match(x) ]
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 [ IntrospectionParser(x, self.dbus) \
- 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()