| #!/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([ IntrospectionParser(name, self.bus.dbus) ]) |
| |
| 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 add_match_interfaces(self, owner, path, interfaces): |
| for x in interfaces: |
| if self.intf_match not in x: |
| continue |
| |
| self.cache.add_item((path, owner, x)) |
| |
| def add_match_items(self, owner, bus_items): |
| for x,y in bus_items.iteritems(): |
| self.add_match_interfaces(owner, x, 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 |
| 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.add_match_items(o.name, o.introspect()) |
| |
| 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 IntrospectionNodeParser: |
| def __init__(self, data): |
| self.data = data |
| self.cache = {} |
| |
| def parse_args(self): |
| return [ x.attrib for x in self.data.findall('arg') ] |
| |
| def parse_children(self): |
| return [ x.attrib['name'] for x in self.data.findall('node') ] |
| |
| def parse_method_or_signal(self): |
| name = self.data.attrib['name'] |
| return name, self.parse_args() |
| |
| def parse_interface(self): |
| 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 |
| n, element = p.parse_method_or_signal() |
| iface[node.tag][n] = element |
| |
| return name, iface |
| |
| def parse_node(self): |
| if self.cache: |
| return self.cache |
| |
| self.cache['interfaces'] = {} |
| 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 |
| elif node.tag == 'node': |
| self.cache['children'] = self.parse_children() |
| |
| return self.cache |
| |
| def get_interfaces(self): |
| return self.parse_node()['interfaces'] |
| |
| def get_children(self): |
| return self.parse_node()['children'] |
| |
| def recursive_binding(self): |
| return any('/' in s for s in self.get_children()) |
| |
| class IntrospectionParser: |
| def __init__(self, name, bus): |
| self.name = name |
| self.bus = bus |
| |
| def _introspect(self, path): |
| try: |
| obj = self.bus.get_object(self.name, path) |
| iface = dbus.Interface(obj, dbus.BUS_DAEMON_IFACE + '.Introspectable') |
| data = iface.Introspect() |
| except dbus.DBusException: |
| return None |
| |
| return IntrospectionNodeParser(ElementTree.fromstring(data)) |
| |
| def _discover_flat(self, path, parser): |
| items = {} |
| interfaces = parser.get_interfaces().keys() |
| if interfaces: |
| items[path] = {} |
| items[path]['interfaces'] = interfaces |
| |
| return items |
| |
| def introspect(self, path = '/', parser = None): |
| items = {} |
| if not parser: |
| parser = self._introspect(path) |
| if not parser: |
| return {} |
| items.update(self._discover_flat(path, parser)) |
| |
| if path != '/': |
| path += '/' |
| |
| if parser.recursive_binding(): |
| callback = self._discover_flat |
| else: |
| callback = self.introspect |
| |
| for k in parser.get_children(): |
| parser = self._introspect(path + k) |
| if not parser: |
| continue |
| items.update(callback(path + k, parser)) |
| |
| 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 [ 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() |
| |
| loop.run() |