All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH phosphor-objmgr 0/6] association support
@ 2016-04-13 20:50 OpenBMC Patches
  2016-04-13 20:50 ` [PATCH phosphor-objmgr 1/6] Add gitignore OpenBMC Patches
                   ` (5 more replies)
  0 siblings, 6 replies; 8+ messages in thread
From: OpenBMC Patches @ 2016-04-13 20:50 UTC (permalink / raw)
  To: openbmc



<!-- Reviewable:start -->
This change is [<img src="https://reviewable.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/openbmc/phosphor-objmgr/8)
<!-- Reviewable:end -->


https://github.com/openbmc/phosphor-objmgr/pull/8

Brad Bishop (6):
  Add gitignore
  Use pyobmc package
  Update license year from 2015 to 2016
  Add cache access and signal validation wrappers
  Minor refactoring
  Add support for associations

 .gitignore       |  87 +++++++++++
 OpenBMCMapper.py | 364 --------------------------------------------
 phosphor-mapper  | 457 +++++++++++++++++++++++++++++++++++++++++++++----------
 setup.py         |   3 +-
 4 files changed, 463 insertions(+), 448 deletions(-)
 create mode 100644 .gitignore
 delete mode 100644 OpenBMCMapper.py

-- 
2.7.1

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH phosphor-objmgr 1/6] Add gitignore
  2016-04-13 20:50 [PATCH phosphor-objmgr 0/6] association support OpenBMC Patches
@ 2016-04-13 20:50 ` OpenBMC Patches
  2016-04-13 20:50 ` [PATCH phosphor-objmgr 2/6] Use pyobmc package OpenBMC Patches
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: OpenBMC Patches @ 2016-04-13 20:50 UTC (permalink / raw)
  To: openbmc; +Cc: Brad Bishop

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

Lifted from
https://github.com/github/gitignore/blob/master/Python.gitignore
---
 .gitignore | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 87 insertions(+)
 create mode 100644 .gitignore

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7c0d83f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,87 @@
+# Copyright (c) 2016 GitHub, Inc.
+#
+# Permission is hereby granted,  free of charge,  to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to  use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+# source: https://github.com/github/gitignore/blob/master/Python.gitignore
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+.hypothesis/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+#Ipython Notebook
+.ipynb_checkpoints
+
+*.swo
+*.swp
-- 
2.7.1

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH phosphor-objmgr 2/6] Use pyobmc package
  2016-04-13 20:50 [PATCH phosphor-objmgr 0/6] association support OpenBMC Patches
  2016-04-13 20:50 ` [PATCH phosphor-objmgr 1/6] Add gitignore OpenBMC Patches
@ 2016-04-13 20:50 ` OpenBMC Patches
  2016-04-13 20:50 ` [PATCH phosphor-objmgr 3/6] Update license year from 2015 to 2016 OpenBMC Patches
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: OpenBMC Patches @ 2016-04-13 20:50 UTC (permalink / raw)
  To: openbmc; +Cc: Brad Bishop

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

No functional changes here.  Just module/package namespace updates
to use the pyobmc library.  Removed code duplicated by pyobmc.
---
 OpenBMCMapper.py | 364 -------------------------------------------------------
 phosphor-mapper  |  28 +++--
 setup.py         |   3 +-
 3 files changed, 16 insertions(+), 379 deletions(-)
 delete mode 100644 OpenBMCMapper.py

diff --git a/OpenBMCMapper.py b/OpenBMCMapper.py
deleted file mode 100644
index 518e703..0000000
--- a/OpenBMCMapper.py
+++ /dev/null
@@ -1,364 +0,0 @@
-#!/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.
-
-from xml.etree import ElementTree
-import dbus
-
-MAPPER_NAME = 'org.openbmc.objectmapper'
-MAPPER_IFACE = MAPPER_NAME + '.ObjectMapper'
-MAPPER_PATH = '/org/openbmc/objectmapper/objectmapper'
-ENUMERATE_IFACE = 'org.openbmc.Object.Enumerate'
-MAPPER_NOT_FOUND = 'org.openbmc.objectmapper.Error.NotFound'
-
-
-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])
-
-
-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, 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')]
-
-    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'] = {}
-
-        for node in self.data:
-            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 iface
-
-    def parse_node(self):
-        if self.cache:
-            return self.cache
-
-        self.cache['interfaces'] = {}
-        self.cache['children'] = []
-
-        for node in self.data:
-            if node.tag == 'interface':
-                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()
-
-        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, 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, introspect=False)
-            iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE)
-            data = iface.Introspect()
-        except dbus.DBusException:
-            return None
-
-        return IntrospectionNodeParser(
-            ElementTree.fromstring(data),
-            self.tag_match,
-            self.intf_match)
-
-    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 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 255897f..d81fbf6 100644
--- a/phosphor-mapper
+++ b/phosphor-mapper
@@ -21,12 +21,14 @@ import dbus.service
 import dbus.exceptions
 import dbus.mainloop.glib
 import gobject
-from OpenBMCMapper import IntrospectionParser, PathTree
-import OpenBMCMapper
+from obmc.dbuslib.introspection import IntrospectionParser
+import obmc.utils.pathtree
+import obmc.utils.misc
+import obmc.mapper
 
 
 class MapperNotFoundException(dbus.exceptions.DBusException):
-    _dbus_error_name = OpenBMCMapper.MAPPER_NOT_FOUND
+    _dbus_error_name = obmc.mapper.MAPPER_NOT_FOUND
 
     def __init__(self, path):
         super(MapperNotFoundException, self).__init__(
@@ -35,14 +37,14 @@ class MapperNotFoundException(dbus.exceptions.DBusException):
 
 class ObjectMapper(dbus.service.Object):
     def __init__(self, bus, path,
-                 name_match=OpenBMCMapper.org_dot_openbmc_match,
-                 intf_match=OpenBMCMapper.org_dot_openbmc_match):
+                 name_match=obmc.utils.misc.org_dot_openbmc_match,
+                 intf_match=obmc.utils.misc.org_dot_openbmc_match):
         super(ObjectMapper, self).__init__(bus.dbus, path)
-        self.cache = PathTree()
+        self.cache = obmc.utils.pathtree.PathTree()
         self.bus = bus
         self.name_match = name_match
         self.intf_match = intf_match
-        self.tag_match = OpenBMCMapper.ListMatch(['children', 'interface'])
+        self.tag_match = obmc.utils.misc.ListMatch(['children', 'interface'])
         self.service = None
 
         gobject.idle_add(self.discover)
@@ -64,7 +66,7 @@ class ObjectMapper(dbus.service.Object):
             sender_keyword='sender')
 
     def bus_match(self, name):
-        if name == OpenBMCMapper.MAPPER_NAME:
+        if name == obmc.mapper.MAPPER_NAME:
             return False
         return self.name_match(name)
 
@@ -158,23 +160,23 @@ class ObjectMapper(dbus.service.Object):
         if self.discovery_pending():
             print "ObjectMapper discovery complete..."
             self.service = dbus.service.BusName(
-                OpenBMCMapper.MAPPER_NAME, self.bus.dbus)
+                obmc.mapper.MAPPER_NAME, self.bus.dbus)
 
-    @dbus.service.method(OpenBMCMapper.MAPPER_IFACE, 's', 'a{sas}')
+    @dbus.service.method(obmc.mapper.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, 'si', 'as')
+    @dbus.service.method(obmc.mapper.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, 'si', 'a{sa{sas}}')
+    @dbus.service.method(obmc.mapper.MAPPER_IFACE, 'si', 'a{sa{sas}}')
     def GetSubTree(self, path, depth):
         try:
             return {x: y for x, y in self.cache.dataitems(path, depth)}
@@ -205,7 +207,7 @@ class BusWrapper:
 if __name__ == '__main__':
     dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
     bus = dbus.SystemBus()
-    o = ObjectMapper(BusWrapper(bus), OpenBMCMapper.MAPPER_PATH)
+    o = ObjectMapper(BusWrapper(bus), obmc.mapper.MAPPER_PATH)
     loop = gobject.MainLoop()
 
     loop.run()
diff --git a/setup.py b/setup.py
index bfbf3fc..7185072 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,5 @@
 from distutils.core import setup
-setup(name='OpenBMCMapper',
+setup(name='phosphor-mapper',
       version='1.0',
-      py_modules=['OpenBMCMapper'],
       scripts=['phosphor-mapper']
       )
-- 
2.7.1

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH phosphor-objmgr 3/6] Update license year from 2015 to 2016
  2016-04-13 20:50 [PATCH phosphor-objmgr 0/6] association support OpenBMC Patches
  2016-04-13 20:50 ` [PATCH phosphor-objmgr 1/6] Add gitignore OpenBMC Patches
  2016-04-13 20:50 ` [PATCH phosphor-objmgr 2/6] Use pyobmc package OpenBMC Patches
@ 2016-04-13 20:50 ` OpenBMC Patches
  2016-04-13 20:50 ` [PATCH phosphor-objmgr 4/6] Add cache access and signal validation wrappers OpenBMC Patches
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: OpenBMC Patches @ 2016-04-13 20:50 UTC (permalink / raw)
  To: openbmc; +Cc: Brad Bishop

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

---
 phosphor-mapper | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/phosphor-mapper b/phosphor-mapper
index d81fbf6..5995ee0 100644
--- a/phosphor-mapper
+++ b/phosphor-mapper
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-# Contributors Listed Below - COPYRIGHT 2015
+# Contributors Listed Below - COPYRIGHT 2016
 # [+] International Business Machines Corp.
 #
 #
-- 
2.7.1

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH phosphor-objmgr 4/6] Add cache access and signal validation wrappers
  2016-04-13 20:50 [PATCH phosphor-objmgr 0/6] association support OpenBMC Patches
                   ` (2 preceding siblings ...)
  2016-04-13 20:50 ` [PATCH phosphor-objmgr 3/6] Update license year from 2015 to 2016 OpenBMC Patches
@ 2016-04-13 20:50 ` OpenBMC Patches
  2016-04-13 20:50 ` [PATCH phosphor-objmgr 5/6] Minor refactoring OpenBMC Patches
  2016-04-13 20:50 ` [PATCH phosphor-objmgr 6/6] Add support for associations OpenBMC Patches
  5 siblings, 0 replies; 8+ messages in thread
From: OpenBMC Patches @ 2016-04-13 20:50 UTC (permalink / raw)
  To: openbmc; +Cc: Brad Bishop

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

Use of the main cache structure will grow so added some wrappers
that automate initialization and cleanup.

Refactored duplicate signal handler bus/interface validation
logic into reusable functions.

Updated discovery and signal handlers to make use of all these
new functions.
---
 phosphor-mapper | 139 +++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 82 insertions(+), 57 deletions(-)

diff --git a/phosphor-mapper b/phosphor-mapper
index 5995ee0..bf0e832 100644
--- a/phosphor-mapper
+++ b/phosphor-mapper
@@ -25,6 +25,7 @@ from obmc.dbuslib.introspection import IntrospectionParser
 import obmc.utils.pathtree
 import obmc.utils.misc
 import obmc.mapper
+import obmc.dbuslib.bindings
 
 
 class MapperNotFoundException(dbus.exceptions.DBusException):
@@ -74,41 +75,22 @@ class ObjectMapper(dbus.service.Object):
         return not bool(self.service)
 
     def interfaces_added_handler(self, path, iprops, **kw):
-        name = self.bus.get_owned_name(self.bus_match, kw['sender'])
-        if self.discovery_pending() or \
-                not self.bus_match(name):
-            return
-
-        matches = [x for x in iprops.iterkeys() if self.intf_match(x)]
-        self.add_interfaces(path, kw['sender'], matches)
+        path = str(path)
+        owner = str(kw['sender'])
+        interfaces = self.get_signal_interfaces(owner, iprops.iterkeys())
+        cache_entry = self.cache.get(path, {})
+        old = self.interfaces_get(cache_entry, owner)
+        new = list(set(interfaces).union(old))
+        self.update_interfaces(path, owner, old, new)
 
     def interfaces_removed_handler(self, path, interfaces, **kw):
-        name = self.bus.get_owned_name(self.bus_match, kw['sender'])
-        if self.discovery_pending() or \
-                not self.bus_match(name):
-            return
-        item = self.cache[path]
-        sender = kw['sender']
-        for x in interfaces:
-            if self.intf_match(x):
-                try:
-                    item[sender].remove(x)
-                except ValueError:
-                    pass
-
-        # remove the owner if there aren't any interfaces left
-        if not item[sender]:
-            del item[sender]
-
-        # update if interfaces remain
-        if item:
-            self.cache[path] = item
-        # mark for removal if no interfaces remain
-        elif self.cache.get_children(path):
-            self.cache.demote(path)
-        # delete the entire path if everything is gone
-        else:
-            del self.cache[path]
+        path = str(path)
+        owner = str(kw['sender'])
+        interfaces = self.get_signal_interfaces(owner, interfaces)
+        cache_entry = self.cache.get(path, {})
+        old = self.interfaces_get(cache_entry, owner)
+        new = list(set(old).difference(interfaces))
+        self.update_interfaces(path, owner, old, new)
 
     def process_new_owner(self, name):
         # unique name
@@ -118,35 +100,34 @@ class ObjectMapper(dbus.service.Object):
                              self.intf_match)])
 
     def process_old_owner(self, 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 self.discovery_pending() or \
-                not self.bus_match(service):
-            return
-
-        if new:
+        for path, item in self.cache.dataitems():
+            old = self.interfaces_get(item, name)
+            # remove all interfaces for this service
+            self.update_interfaces(
+                path, name, old=old, new=[])
+
+    def bus_handler(self, owner, old, new):
+        valid = False
+        if not obmc.dbuslib.bindings.is_unique(owner):
+            valid = self.valid_signal(owner)
+
+        if valid and new:
             self.process_new_owner(new)
-        if old:
+        if valid and old:
             self.process_old_owner(old)
 
-    def add_interfaces(self, path, owner, interfaces):
-        d = self.cache.setdefault(path, {})
-        d = d.setdefault(owner, [])
-        self.cache[path][owner] = list(set(d + interfaces))
+    def update_interfaces(self, path, owner, old, new):
+        cache_entry = self.cache.setdefault(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)
 
     def add_items(self, owner, bus_items):
-        for x, y in bus_items.iteritems():
-            self.add_interfaces(x, owner, y['interfaces'])
+        for path, items in bus_items.iteritems():
+            # convert dbus types to native.
+            interfaces = [str(i) for i in items.get('interfaces', [])]
+            self.update_interfaces(path, str(owner), old=[], new=interfaces)
 
     def discover(self, owners=None):
         if not owners:
@@ -162,6 +143,50 @@ class ObjectMapper(dbus.service.Object):
             self.service = dbus.service.BusName(
                 obmc.mapper.MAPPER_NAME, self.bus.dbus)
 
+    def valid_signal(self, owner):
+        if obmc.dbuslib.bindings.is_unique(owner):
+            owner = self.bus.get_owned_name(self.bus_match, owner)
+
+        return owner is not None and not self.discovery_pending() and \
+            self.bus_match(owner)
+
+    def get_signal_interfaces(self, owner, interfaces):
+        filtered = []
+        if self.valid_signal(owner):
+            filtered = [str(x) for x in interfaces if self.intf_match(x)]
+
+        return filtered
+
+    @staticmethod
+    def interfaces_get(item, owner, default=[]):
+        return item.get(owner, default)
+
+    @staticmethod
+    def interfaces_append(item, owner, append):
+        interfaces = item.setdefault(owner, [])
+        item[owner] = list(set(append).union(interfaces))
+
+    def interfaces_remove(self, item, owner, remove, path):
+        interfaces = item.get(owner, [])
+        item[owner] = list(set(interfaces).difference(remove))
+
+        if not item[owner]:
+            # remove the owner if there aren't any interfaces left
+            del item[owner]
+
+        if item:
+            # other owners remain
+            return
+
+        if self.cache.get_children(path):
+            # there are still references to this path
+            # from objects further down the tree.
+            # mark it for removal if that changes
+            self.cache.demote(path)
+        else:
+            # delete the entire path if everything is gone
+            del self.cache[path]
+
     @dbus.service.method(obmc.mapper.MAPPER_IFACE, 's', 'a{sas}')
     def GetObject(self, path):
         o = self.cache.get(path)
-- 
2.7.1

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH phosphor-objmgr 5/6] Minor refactoring
  2016-04-13 20:50 [PATCH phosphor-objmgr 0/6] association support OpenBMC Patches
                   ` (3 preceding siblings ...)
  2016-04-13 20:50 ` [PATCH phosphor-objmgr 4/6] Add cache access and signal validation wrappers OpenBMC Patches
@ 2016-04-13 20:50 ` OpenBMC Patches
  2016-04-13 20:50 ` [PATCH phosphor-objmgr 6/6] Add support for associations OpenBMC Patches
  5 siblings, 0 replies; 8+ messages in thread
From: OpenBMC Patches @ 2016-04-13 20:50 UTC (permalink / raw)
  To: openbmc; +Cc: Brad Bishop

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

No functional changes.
Changed any bus name references to "owner".
A couple whitespace changes.
---
 phosphor-mapper | 32 +++++++++++++++++---------------
 1 file changed, 17 insertions(+), 15 deletions(-)

diff --git a/phosphor-mapper b/phosphor-mapper
index bf0e832..a00beaa 100644
--- a/phosphor-mapper
+++ b/phosphor-mapper
@@ -33,7 +33,7 @@ class MapperNotFoundException(dbus.exceptions.DBusException):
 
     def __init__(self, path):
         super(MapperNotFoundException, self).__init__(
-            "path or object not found: %s" % (path))
+            "path or object not found: %s" % path)
 
 
 class ObjectMapper(dbus.service.Object):
@@ -66,10 +66,10 @@ class ObjectMapper(dbus.service.Object):
             signal_name='InterfacesRemoved',
             sender_keyword='sender')
 
-    def bus_match(self, name):
-        if name == obmc.mapper.MAPPER_NAME:
-            return False
-        return self.name_match(name)
+    def bus_match(self, owner):
+        # Ignore my own signals
+        return owner != obmc.mapper.MAPPER_NAME and \
+            self.name_match(owner)
 
     def discovery_pending(self):
         return not bool(self.service)
@@ -92,19 +92,19 @@ class ObjectMapper(dbus.service.Object):
         new = list(set(old).difference(interfaces))
         self.update_interfaces(path, owner, old, new)
 
-    def process_new_owner(self, name):
+    def process_new_owner(self, owner):
         # unique name
-        return self.discover([IntrospectionParser(name,
+        return self.discover([IntrospectionParser(owner,
                              self.bus.dbus,
                              self.tag_match,
                              self.intf_match)])
 
-    def process_old_owner(self, name):
+    def process_old_owner(self, owner):
         for path, item in self.cache.dataitems():
-            old = self.interfaces_get(item, name)
+            old = self.interfaces_get(item, owner)
             # remove all interfaces for this service
             self.update_interfaces(
-                path, name, old=old, new=[])
+                path, owner, old=old, new=[])
 
     def bus_handler(self, owner, old, new):
         valid = False
@@ -129,12 +129,14 @@ class ObjectMapper(dbus.service.Object):
             interfaces = [str(i) for i in items.get('interfaces', [])]
             self.update_interfaces(path, str(owner), old=[], new=interfaces)
 
-    def discover(self, owners=None):
+    def discover(self, owners=[]):
         if not owners:
-            owners = [IntrospectionParser(x, self.bus.dbus,
-                                          self.tag_match,
-                                          self.intf_match)
-                      for x in self.bus.get_owner_names(self.bus_match)]
+            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())
 
-- 
2.7.1

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH phosphor-objmgr 6/6] Add support for associations
  2016-04-13 20:50 [PATCH phosphor-objmgr 0/6] association support OpenBMC Patches
                   ` (4 preceding siblings ...)
  2016-04-13 20:50 ` [PATCH phosphor-objmgr 5/6] Minor refactoring OpenBMC Patches
@ 2016-04-13 20:50 ` OpenBMC Patches
  5 siblings, 0 replies; 8+ messages in thread
From: OpenBMC Patches @ 2016-04-13 20:50 UTC (permalink / raw)
  To: openbmc; +Cc: Brad Bishop

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

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
---
 phosphor-mapper | 266 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 265 insertions(+), 1 deletion(-)

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):
-- 
2.7.1

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH phosphor-objmgr 6/6] Add support for associations
  2016-03-18 17:30 [PATCH phosphor-objmgr 0/6] association support OpenBMC Patches
@ 2016-03-18 17:30 ` OpenBMC Patches
  0 siblings, 0 replies; 8+ messages in thread
From: OpenBMC Patches @ 2016-03-18 17:30 UTC (permalink / raw)
  To: openbmc; +Cc: Brad Bishop

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

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
---
 phosphor-mapper | 266 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 265 insertions(+), 1 deletion(-)

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):
-- 
2.7.1

^ permalink raw reply related	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2016-04-13 20:51 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-04-13 20:50 [PATCH phosphor-objmgr 0/6] association support OpenBMC Patches
2016-04-13 20:50 ` [PATCH phosphor-objmgr 1/6] Add gitignore OpenBMC Patches
2016-04-13 20:50 ` [PATCH phosphor-objmgr 2/6] Use pyobmc package OpenBMC Patches
2016-04-13 20:50 ` [PATCH phosphor-objmgr 3/6] Update license year from 2015 to 2016 OpenBMC Patches
2016-04-13 20:50 ` [PATCH phosphor-objmgr 4/6] Add cache access and signal validation wrappers OpenBMC Patches
2016-04-13 20:50 ` [PATCH phosphor-objmgr 5/6] Minor refactoring OpenBMC Patches
2016-04-13 20:50 ` [PATCH phosphor-objmgr 6/6] Add support for associations OpenBMC Patches
  -- strict thread matches above, loose matches on Subject: below --
2016-03-18 17:30 [PATCH phosphor-objmgr 0/6] association support OpenBMC Patches
2016-03-18 17:30 ` [PATCH phosphor-objmgr 6/6] Add support for associations OpenBMC Patches

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.