diff options
author | Brad Bishop <bradleyb@us.ibm.com> | 2016-03-18 12:24:42 -0400 |
---|---|---|
committer | Brad Bishop <bradleyb@us.ibm.com> | 2016-03-18 12:24:42 -0400 |
commit | d9fc21c792f810db3479b46b7a6acf231d90a75b (patch) | |
tree | a7ac34678554840baa925df4c7e163898e22af12 /phosphor-mapper | |
parent | 9cff26bb5f801e07d5e6ae83847051ead4d74df2 (diff) | |
download | phosphor-objmgr-d9fc21c792f810db3479b46b7a6acf231d90a75b.tar.gz phosphor-objmgr-d9fc21c792f810db3479b46b7a6acf231d90a75b.zip |
Add support for associations
General overview:
Monitor objects that implement org.openbmc.Associations.
React by creating objects implementing org.openbmc.Association.
Include org.freedesktop.DBus.ObjectManager support.
Details:
Implemented properties changed handler for changes to associations
property of org.openbmc.Associations objects.
Updated interfaces added/removed handlers to react to
org.openbmc.Associations.
Required an index to react to endpoint state changes.
For additional on associations information check:
https://github.com/openbmc/docs/blob/master/dbus-interfaces.md
Diffstat (limited to 'phosphor-mapper')
-rw-r--r-- | phosphor-mapper | 266 |
1 files changed, 265 insertions, 1 deletions
diff --git a/phosphor-mapper b/phosphor-mapper index a00beaa..77b0f7c 100644 --- a/phosphor-mapper +++ b/phosphor-mapper @@ -26,6 +26,7 @@ import obmc.utils.pathtree import obmc.utils.misc import obmc.mapper import obmc.dbuslib.bindings +import obmc.dbuslib.enums class MapperNotFoundException(dbus.exceptions.DBusException): @@ -36,6 +37,59 @@ class MapperNotFoundException(dbus.exceptions.DBusException): "path or object not found: %s" % path) +class Association(dbus.service.Object): + def __init__(self, bus, path, endpoints): + super(Association, self).__init__(bus, path) + self.endpoints = endpoints + + def __getattr__(self, name): + if name == 'properties': + return { + obmc.dbuslib.enums.OBMC_ASSOC_IFACE: { + 'endpoints': self.endpoints}} + return super(Association, self).__getattr__(name) + + def emit_signal(self, old): + if old != self.endpoints: + self.PropertiesChanged( + obmc.dbuslib.enums.OBMC_ASSOC_IFACE, + {'endpoints': self.endpoints}, ['endpoints']) + + def append(self, endpoints): + old = self.endpoints + self.endpoints = list(set(endpoints).union(self.endpoints)) + self.emit_signal(old) + + def remove(self, endpoints): + old = self.endpoints + self.endpoints = list(set(self.endpoints).difference(endpoints)) + self.emit_signal(old) + + @dbus.service.method(dbus.PROPERTIES_IFACE, 'ss', 'as') + def Get(self, interface_name, property_name): + if property_name != 'endpoints': + raise dbus.exceptions.DBusException(name=DBUS_UNKNOWN_PROPERTY) + return self.GetAll(interface_name)[property_name] + + @dbus.service.method(dbus.PROPERTIES_IFACE, 's', 'a{sas}') + def GetAll(self, interface_name): + if interface_name != obmc.dbuslib.enums.OBMC_ASSOC_IFACE: + raise dbus.exceptions.DBusException(DBUS_UNKNOWN_INTERFACE) + return {'endpoints': self.endpoints} + + @dbus.service.signal( + dbus.PROPERTIES_IFACE, signature='sa{sas}as') + def PropertiesChanged( + self, interface_name, changed_properties, invalidated_properties): + pass + + +class Manager(obmc.dbuslib.bindings.DbusObjectManager): + def __init__(self, bus, path): + obmc.dbuslib.bindings.DbusObjectManager.__init__(self) + dbus.service.Object.__init__(self, bus.dbus, path) + + class ObjectMapper(dbus.service.Object): def __init__(self, bus, path, name_match=obmc.utils.misc.org_dot_openbmc_match, @@ -47,6 +101,9 @@ class ObjectMapper(dbus.service.Object): self.intf_match = intf_match self.tag_match = obmc.utils.misc.ListMatch(['children', 'interface']) self.service = None + self.index = {} + self.manager = Manager(bus, obmc.dbuslib.bindings.OBJ_PREFIX) + self.unique = bus.dbus.get_unique_name() gobject.idle_add(self.discover) self.bus.dbus.add_signal_receiver( @@ -65,6 +122,12 @@ class ObjectMapper(dbus.service.Object): dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager', signal_name='InterfacesRemoved', sender_keyword='sender') + self.bus.dbus.add_signal_receiver( + self.properties_changed_handler, + dbus_interface=dbus.PROPERTIES_IFACE, + signal_name='PropertiesChanged', + path_keyword='path', + sender_keyword='sender') def bus_match(self, owner): # Ignore my own signals @@ -92,6 +155,23 @@ class ObjectMapper(dbus.service.Object): new = list(set(old).difference(interfaces)) self.update_interfaces(path, owner, old, new) + def properties_changed_handler(self, interface, new, old, **kw): + owner = str(kw['sender']) + path = str(kw['path']) + interfaces = self.get_signal_interfaces(owner, [interface]) + if not self.is_association(interfaces): + return + associations = new.get('associations', None) + if associations is None: + return + + associations = [ + (str(x), str(y), str(z)) for x, y, z in associations] + self.update_associations( + path, owner, + self.index_get_associations(path, [owner]), + associations) + def process_new_owner(self, owner): # unique name return self.discover([IntrospectionParser(owner, @@ -118,10 +198,22 @@ class ObjectMapper(dbus.service.Object): def update_interfaces(self, path, owner, old, new): cache_entry = self.cache.setdefault(path, {}) + created = [] if self.has_interfaces(cache_entry) else [path] added = list(set(new).difference(old)) removed = list(set(old).difference(new)) self.interfaces_append(cache_entry, owner, added) self.interfaces_remove(cache_entry, owner, removed, path) + destroyed = [] if self.has_interfaces(cache_entry) else [path] + + # react to anything that requires association updates + new_assoc = [] + old_assoc = [] + if self.is_association(added): + new_assoc = self.dbus_get_associations(path, owner) + if self.is_association(removed): + old_assoc = self.index_get_associations(path, [owner]) + self.update_associations( + path, owner, old_assoc, new_assoc, created, destroyed) def add_items(self, owner, bus_items): for path, items in bus_items.iteritems(): @@ -130,17 +222,27 @@ class ObjectMapper(dbus.service.Object): self.update_interfaces(path, str(owner), old=[], new=interfaces) def discover(self, owners=[]): + def match(iface): + return iface == dbus.BUS_DAEMON_IFACE + '.ObjectManager' or \ + self.intf_match(iface) if not owners: owners = [ IntrospectionParser( x, self.bus.dbus, self.tag_match, - self.intf_match) + match) for x in self.bus.get_owner_names(self.bus_match)] for o in owners: self.add_items(o.name, o.introspect()) if self.discovery_pending(): + # add my object mananger instance + self.add_items( + self.unique, + {obmc.dbuslib.bindings.OBJ_PREFIX: + {'interfaces': + [dbus.BUS_DAEMON_IFACE + '.ObjectManager']}}) + print "ObjectMapper discovery complete..." self.service = dbus.service.BusName( obmc.mapper.MAPPER_NAME, self.bus.dbus) @@ -210,6 +312,168 @@ class ObjectMapper(dbus.service.Object): except KeyError: raise MapperNotFoundException(path) + @staticmethod + def has_interfaces(item): + for owner in item.iterkeys(): + if ObjectMapper.interfaces_get(item, owner): + return True + return False + + @staticmethod + def is_association(interfaces): + return obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE in interfaces + + def index_get(self, index, path, owners): + items = [] + item = self.index.get(index, {}) + item = item.get(path, {}) + for o in owners: + items.extend(item.get(o, [])) + return items + + def index_append(self, index, path, owner, assoc): + item = self.index.setdefault(index, {}) + item = item.setdefault(path, {}) + item = item.setdefault(owner, []) + item.append(assoc) + + def index_remove(self, index, path, owner, assoc): + index = self.index.get(index, {}) + owners = index.get(path, {}) + items = owners.get(owner, []) + if assoc in items: + items.remove(assoc) + if not items: + del owners[owner] + if not owners: + del index[path] + + def dbus_get_associations(self, path, owner): + obj = self.bus.dbus.get_object(owner, path, introspect=False) + iface = dbus.Interface(obj, dbus.PROPERTIES_IFACE) + return [(str(f), str(r), str(e)) for f, r, e in iface.Get( + obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE, + 'associations')] + + def index_get_associations(self, path, owners=[], direction='forward'): + forward = 'forward' if direction == 'forward' else 'reverse' + reverse = 'reverse' if direction == 'forward' else 'forward' + + associations = [] + if not owners: + index = self.index.get(forward, {}) + owners = index.get(path, {}).keys() + + # f: forward + # r: reverse + for rassoc in self.index_get(forward, path, owners): + elements = rassoc.split('/') + rtype = ''.join(elements[-1:]) + fendpoint = '/'.join(elements[:-1]) + for fassoc in self.index_get(reverse, fendpoint, owners): + elements = fassoc.split('/') + ftype = ''.join(elements[-1:]) + rendpoint = '/'.join(elements[:-1]) + if rendpoint != path: + continue + associations.append((ftype, rtype, fendpoint)) + + return associations + + def update_association(self, path, removed, added): + iface = obmc.dbuslib.enums.OBMC_ASSOC_IFACE + create = [] if self.manager.get(path, False) else [iface] + + if added and create: + self.manager.add( + path, Association(self.bus.dbus, path, added)) + elif added: + self.manager.get(path).append(added) + + obj = self.manager.get(path, None) + if obj and removed: + obj.remove(removed) + + if obj and not obj.endpoints: + self.manager.remove(path) + + delete = [] if self.manager.get(path, False) else [iface] + + if create != delete: + self.update_interfaces( + path, self.unique, delete, create) + + def update_associations( + self, path, owner, old, new, created=[], destroyed=[]): + added = list(set(new).difference(old)) + removed = list(set(old).difference(new)) + for forward, reverse, endpoint in added: + # update the index + forward_path = str(path + '/' + forward) + reverse_path = str(endpoint + '/' + reverse) + self.index_append( + 'forward', path, owner, reverse_path) + self.index_append( + 'reverse', endpoint, owner, forward_path) + + # create the association if the endpoint exists + if self.cache.get(endpoint, None) is None: + continue + + self.update_association(forward_path, [], [endpoint]) + self.update_association(reverse_path, [], [path]) + + for forward, reverse, endpoint in removed: + # update the index + forward_path = str(path + '/' + forward) + reverse_path = str(endpoint + '/' + reverse) + self.index_remove( + 'forward', path, owner, reverse_path) + self.index_remove( + 'reverse', endpoint, owner, forward_path) + + # destroy the association if it exists + self.update_association(forward_path, [endpoint], []) + self.update_association(reverse_path, [path], []) + + # If the associations interface endpoint comes + # or goes create or destroy the appropriate + # associations + for path in created: + for forward, reverse, endpoint in \ + self.index_get_associations(path, direction='reverse'): + forward_path = str(path + '/' + forward) + reverse_path = str(endpoint + '/' + reverse) + self.update_association(forward_path, [], [endpoint]) + self.update_association(reverse_path, [], [path]) + + for path in destroyed: + for forward, reverse, endpoint in \ + self.index_get_associations(path, direction='reverse'): + forward_path = str(path + '/' + forward) + reverse_path = str(endpoint + '/' + reverse) + self.update_association(forward_path, [endpoint], []) + self.update_association(reverse_path, [path], []) + + @dbus.service.method(obmc.mapper.MAPPER_IFACE, 's', 'a{sa{sas}}') + def GetAncestors(self, path): + elements = filter(bool, path.split('/')) + paths = [] + objs = {} + while elements: + elements.pop() + paths.append('/' + '/'.join(elements)) + if path != '/': + paths.append('/') + + for path in paths: + obj = self.cache.get(path, None) + if obj is None: + continue + objs[path] = obj + + return objs + class BusWrapper: def __init__(self, bus): |