blob: 313baa22adb8f62be5acd137ab2e428ea022e766 [file] [log] [blame]
Brad Bishop732c6db2015-10-19 14:49:21 -04001#!/usr/bin/env python
2
3# Contributors Listed Below - COPYRIGHT 2015
4# [+] International Business Machines Corp.
5#
6#
7# Licensed under the Apache License, Version 2.0 (the "License");
8# you may not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS,
15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
16# implied. See the License for the specific language governing
17# permissions and limitations under the License.
18
19import sys
20import dbus
21import dbus.service
22import dbus.mainloop.glib
23import gobject
Brad Bishop3f43cdb2015-10-28 22:02:09 -040024from OpenBMCMapper import Path, IntrospectionParser
Brad Bishop732c6db2015-10-19 14:49:21 -040025import OpenBMCMapper
26
27class DictionaryCache:
28 def __init__(self):
29 self.items = {'/': {} }
30
31 def _merge_item(self, item):
32 path, bus, iface = item
33 if bus in self.items[path]:
34 self.items[path][bus].append(iface)
35 else:
36 self.items[path][bus] = [ iface ]
37
38 def add_item(self, item):
39 path, bus, iface = item
40 if path != '/':
41 parent = Path(path).fq(last = -1)
42 if parent not in self.items:
43 self.add_item((parent, None, None))
44
45 items = self.items.get(path, None)
46 if bus and items:
47 self._merge_item(item)
48 elif items:
49 pass
50 elif bus:
51 self.items[path] = { bus: [ iface ] }
52 else:
53 self.items[path] = {}
54
55 def add_items(self, items):
56 for i in items:
57 self.add_item(i)
58
59 def _has_children(self, path):
60 return len(self.get_subtree_paths(path, 1, 'fuzzy')) != 1
61
62 def _try_remove_path(self, path):
63 if path == '/':
64 return None
65
66 if not self._has_children(path):
67 del self.items[path]
68
69 # try and remove the parent
70 parent = Path(path).fq(last = -1)
71 if not self.items[parent]:
72 self._try_remove_path(parent)
73
74 def _remove_bus(self, path, bus):
75 del self.items[path][bus]
76 if not self.items[path]:
77 self._try_remove_path(path)
78
79 def _remove_iface(self, path, bus, iface):
80 self.items[path][bus].remove(iface)
81 if not self.items[path][bus]:
82 self._remove_bus(path, bus)
83
84 def remove_item(self, item):
85 self._remove_iface(*item)
86
87 def remove_items(self, items):
88 for x in items:
89 self.remove_item(x)
90
91 def remove_bus(self, name):
92 for path,x in self.items.items():
93 for bus,ifaces in x.items():
94 if name != bus:
95 continue
96 self.remove_items([ (path, bus, iface) \
97 for iface in ifaces ])
98
99 def remove_busses(self, names):
100 for x in names:
101 self.remove_bus(name)
102
103 def _get_busses(self):
104 busses = [ y.iterkeys() for y in [ x for x in self.items.itervalues() ] ]
105 return set( x for y in busses for x in y ) # flatten nested lists
106
107 def has_bus(self, bus):
108 return any(bus in b for b in self._get_busses())
109
110 def get_subtree_paths(self, root, depth, match_type):
111 return filter(PathMatch(root, depth, match_type),
112 self.items.iterkeys())
113
114 def get_subtree(self, root, depth, match_type):
115 return dict(filter(ObjectMatch(root, depth, match_type),
116 self.items.iteritems()))
117
118 @staticmethod
119 def _iface__str__(ifaces):
120 return '\n'.join([" %s" %(x) for x in ifaces ])
121
122 @staticmethod
123 def _bus__str__(busses):
124 return '\n'.join([ "%s\n%s" %(x, DictionaryCache._iface__str__(y)) \
125 for x,y in busses.iteritems() ])
126
127 def __str__(self):
128 return '\n'.join([ "%s\n%s" %(x, DictionaryCache._bus__str__(y)) \
129 for x,y in self.items.iteritems() ])
130
131class PathMatch(object):
132 def __init__(self, path, depth, match_type):
133 p = Path(path)
134 self.path = p.fq()
135 self.depth = p.depth()
136 self.match_type = match_type
137 self.match_depth = None
138 if depth != -1:
139 self.match_depth = p.depth() + depth
140
141 def __call__(self, path):
142 p = Path(path)
143 depth = p.depth()
144
145 # 'is not None' is really needed because
146 # the match depth can be zero
147 if self.match_depth is not None and depth > self.match_depth:
148 return False
149
150 fuzzy_match = p.fq(last = self.depth) == self.path or \
151 not self.depth
152 depth_match = self.match_depth == depth
153
154 if self.match_type == 'fuzzy':
155 return fuzzy_match
156
157 return fuzzy_match and depth_match
158
159class ObjectMatch(PathMatch):
160 def __init__(self, path, depth, match_type):
161 super(ObjectMatch, self).__init__(path, depth, match_type)
162
163 def __call__(self, tup):
164 if not super(ObjectMatch, self).__call__(tup[0]):
165 return False
166 return tup[1]
167
168class ObjectMapper(dbus.service.Object):
169 def __init__(self, bus, path,
170 name_match = 'org.openbmc',
171 intf_match = 'org.openbmc'):
172 super(ObjectMapper, self).__init__(bus.dbus, path)
173 self.cache = DictionaryCache()
174 self.bus = bus
175 self.name_match = name_match
176 self.intf_match = intf_match
177 self.discovery_done = False
178
179 gobject.idle_add(self.discover)
180 self.bus.dbus.add_signal_receiver(self.bus_handler,
181 dbus_interface = dbus.BUS_DAEMON_IFACE,
182 signal_name = 'NameOwnerChanged')
183 self.bus.dbus.add_signal_receiver(self.interfaces_added_handler,
184 dbus_interface = dbus.BUS_DAEMON_IFACE + '.ObjectManager',
185 signal_name = 'InterfaceesAdded',
186 sender_keyword = 'sender')
187 self.bus.dbus.add_signal_receiver(self.interfaces_removed_handler,
188 dbus_interface = dbus.BUS_DAEMON_IFACE + '.ObjectManager',
189 signal_name = 'InterfacesRemoved',
190 sender_keyword = 'sender')
191
192 def interfaces_added_handler(self, path, iprops, **kw):
193 for x in iprops.iterkeys():
194 if self.intf_match in x:
195 self.cache.add_item((path, kw['sender'], x))
196
197 def interfaces_removed_handler(self, path, interfaces, **kw):
198 for x in interfaces:
199 self.cache.remove_item((path, kw['sender'], x))
200
201 def process_new_owner(self, name):
202 # unique name
Brad Bishop86398d22015-10-28 21:58:31 -0400203 return self.discover([ IntrospectionParser(name, self.bus.dbus) ])
Brad Bishop732c6db2015-10-19 14:49:21 -0400204
205 def process_old_owner(self, name):
206 # unique name
207 self.cache.remove_bus(name)
208
209 def bus_handler(self, service, old, new):
210 if not self.discovery_done or \
211 self.name_match not in service:
212 return
213
214 if new:
215 self.process_new_owner(new)
216 if old:
217 self.process_old_owner(old)
218
Brad Bishop86398d22015-10-28 21:58:31 -0400219 def add_match_interfaces(self, owner, path, interfaces):
220 for x in interfaces:
221 if self.intf_match not in x:
222 continue
223
224 self.cache.add_item((path, owner, x))
225
226 def add_match_items(self, owner, bus_items):
227 for x,y in bus_items.iteritems():
228 self.add_match_interfaces(owner, x, y['interfaces'])
229
Brad Bishop732c6db2015-10-19 14:49:21 -0400230 def discover(self, owners = None):
231 discovery = not self.discovery_done
232 if not owners:
233 owners = self.bus.get_owners(self.name_match)
234 self.discovery_done = True
235 for o in owners:
236
237 # this only happens when an app
238 # grabs more than one well known name
239 if self.cache.has_bus(o.name):
240 continue
241
Brad Bishop86398d22015-10-28 21:58:31 -0400242 self.add_match_items(o.name, o.introspect())
Brad Bishop732c6db2015-10-19 14:49:21 -0400243
244 if discovery:
245 print "ObjectMapper discovery complete..."
246
247 @dbus.service.method(OpenBMCMapper.MAPPER_IFACE, 'sis', 'as')
248 def GetTreePaths(self, path, depth, match_type):
249 return self.cache.get_subtree_paths(path, depth, match_type)
250
251 @dbus.service.method(OpenBMCMapper.MAPPER_IFACE, 'sis', 'a{sa{sas}}')
252 def GetTree(self, path, depth, match_type):
253 return self.cache.get_subtree(path, depth, match_type)
254
255 @dbus.service.method(OpenBMCMapper.MAPPER_IFACE, 'asis', 'a{sa{sas}}')
256 def GetTrees(self, paths, depth, match_type):
257 values = {}
258 for x in paths:
259 values.update(self.GetTree(x, depth, match_type))
260 return values
261
Brad Bishop732c6db2015-10-19 14:49:21 -0400262class BusWrapper:
263 def __init__(self, bus):
264 self.dbus = bus
265
266 def get_service_names(self, match):
267 # these are well known names
268 return [ x for x in self.dbus.list_names() \
269 if match in x and x != OpenBMCMapper.MAPPER_NAME ]
270
271 def get_owner_names(self, match):
272 # these are unique connection names
273 return list( set( [ self.dbus.get_name_owner(x) \
274 for x in self.get_service_names(match) ] ) )
275
276 def get_owners(self, match):
Brad Bishop86398d22015-10-28 21:58:31 -0400277 return [ IntrospectionParser(x, self.dbus) \
Brad Bishop732c6db2015-10-19 14:49:21 -0400278 for x in self.get_owner_names(match) ]
279
280if __name__ == '__main__':
281 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
282 bus = dbus.SystemBus()
283 s = dbus.service.BusName(OpenBMCMapper.MAPPER_NAME, bus)
284 o = ObjectMapper(BusWrapper(bus), OpenBMCMapper.MAPPER_PATH)
285 loop = gobject.MainLoop()
286
287 loop.run()