blob: 890e5468a49f6e5afab15cb569fed2d6d467fef9 [file] [log] [blame]
#!/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()