[PATCH phosphor-objmgr v4] ObjectManager work in progress

OpenBMC Patches patches at stwcx.xyz
Wed Oct 28 09:07:07 AEDT 2015


From: Brad Bishop <bradleyb at us.ibm.com>

Supports:
 initial discovery
 service name ownership changes
 interfaces added and removed signals
 gettreepaths, gettree, gettrees methods
---
 OpenBMCMapper.py |  43 +++++++
 phosphor-mapper  | 338 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 setup.cfg        |   2 +
 setup.py         |   6 +
 4 files changed, 389 insertions(+)
 create mode 100644 OpenBMCMapper.py
 create mode 100644 phosphor-mapper
 create mode 100644 setup.cfg
 create mode 100644 setup.py

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']
+      )
-- 
2.6.0




More information about the openbmc mailing list