blob: 28d260031cb15d2b06e1e5270a5dfa1804de0083 [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
24from xml.etree import ElementTree
25from OpenBMCMapper import Path
26import OpenBMCMapper
27
28class DictionaryCache:
29 def __init__(self):
30 self.items = {'/': {} }
31
32 def _merge_item(self, item):
33 path, bus, iface = item
34 if bus in self.items[path]:
35 self.items[path][bus].append(iface)
36 else:
37 self.items[path][bus] = [ iface ]
38
39 def add_item(self, item):
40 path, bus, iface = item
41 if path != '/':
42 parent = Path(path).fq(last = -1)
43 if parent not in self.items:
44 self.add_item((parent, None, None))
45
46 items = self.items.get(path, None)
47 if bus and items:
48 self._merge_item(item)
49 elif items:
50 pass
51 elif bus:
52 self.items[path] = { bus: [ iface ] }
53 else:
54 self.items[path] = {}
55
56 def add_items(self, items):
57 for i in items:
58 self.add_item(i)
59
60 def _has_children(self, path):
61 return len(self.get_subtree_paths(path, 1, 'fuzzy')) != 1
62
63 def _try_remove_path(self, path):
64 if path == '/':
65 return None
66
67 if not self._has_children(path):
68 del self.items[path]
69
70 # try and remove the parent
71 parent = Path(path).fq(last = -1)
72 if not self.items[parent]:
73 self._try_remove_path(parent)
74
75 def _remove_bus(self, path, bus):
76 del self.items[path][bus]
77 if not self.items[path]:
78 self._try_remove_path(path)
79
80 def _remove_iface(self, path, bus, iface):
81 self.items[path][bus].remove(iface)
82 if not self.items[path][bus]:
83 self._remove_bus(path, bus)
84
85 def remove_item(self, item):
86 self._remove_iface(*item)
87
88 def remove_items(self, items):
89 for x in items:
90 self.remove_item(x)
91
92 def remove_bus(self, name):
93 for path,x in self.items.items():
94 for bus,ifaces in x.items():
95 if name != bus:
96 continue
97 self.remove_items([ (path, bus, iface) \
98 for iface in ifaces ])
99
100 def remove_busses(self, names):
101 for x in names:
102 self.remove_bus(name)
103
104 def _get_busses(self):
105 busses = [ y.iterkeys() for y in [ x for x in self.items.itervalues() ] ]
106 return set( x for y in busses for x in y ) # flatten nested lists
107
108 def has_bus(self, bus):
109 return any(bus in b for b in self._get_busses())
110
111 def get_subtree_paths(self, root, depth, match_type):
112 return filter(PathMatch(root, depth, match_type),
113 self.items.iterkeys())
114
115 def get_subtree(self, root, depth, match_type):
116 return dict(filter(ObjectMatch(root, depth, match_type),
117 self.items.iteritems()))
118
119 @staticmethod
120 def _iface__str__(ifaces):
121 return '\n'.join([" %s" %(x) for x in ifaces ])
122
123 @staticmethod
124 def _bus__str__(busses):
125 return '\n'.join([ "%s\n%s" %(x, DictionaryCache._iface__str__(y)) \
126 for x,y in busses.iteritems() ])
127
128 def __str__(self):
129 return '\n'.join([ "%s\n%s" %(x, DictionaryCache._bus__str__(y)) \
130 for x,y in self.items.iteritems() ])
131
132class PathMatch(object):
133 def __init__(self, path, depth, match_type):
134 p = Path(path)
135 self.path = p.fq()
136 self.depth = p.depth()
137 self.match_type = match_type
138 self.match_depth = None
139 if depth != -1:
140 self.match_depth = p.depth() + depth
141
142 def __call__(self, path):
143 p = Path(path)
144 depth = p.depth()
145
146 # 'is not None' is really needed because
147 # the match depth can be zero
148 if self.match_depth is not None and depth > self.match_depth:
149 return False
150
151 fuzzy_match = p.fq(last = self.depth) == self.path or \
152 not self.depth
153 depth_match = self.match_depth == depth
154
155 if self.match_type == 'fuzzy':
156 return fuzzy_match
157
158 return fuzzy_match and depth_match
159
160class ObjectMatch(PathMatch):
161 def __init__(self, path, depth, match_type):
162 super(ObjectMatch, self).__init__(path, depth, match_type)
163
164 def __call__(self, tup):
165 if not super(ObjectMatch, self).__call__(tup[0]):
166 return False
167 return tup[1]
168
169class ObjectMapper(dbus.service.Object):
170 def __init__(self, bus, path,
171 name_match = 'org.openbmc',
172 intf_match = 'org.openbmc'):
173 super(ObjectMapper, self).__init__(bus.dbus, path)
174 self.cache = DictionaryCache()
175 self.bus = bus
176 self.name_match = name_match
177 self.intf_match = intf_match
178 self.discovery_done = False
179
180 gobject.idle_add(self.discover)
181 self.bus.dbus.add_signal_receiver(self.bus_handler,
182 dbus_interface = dbus.BUS_DAEMON_IFACE,
183 signal_name = 'NameOwnerChanged')
184 self.bus.dbus.add_signal_receiver(self.interfaces_added_handler,
185 dbus_interface = dbus.BUS_DAEMON_IFACE + '.ObjectManager',
186 signal_name = 'InterfaceesAdded',
187 sender_keyword = 'sender')
188 self.bus.dbus.add_signal_receiver(self.interfaces_removed_handler,
189 dbus_interface = dbus.BUS_DAEMON_IFACE + '.ObjectManager',
190 signal_name = 'InterfacesRemoved',
191 sender_keyword = 'sender')
192
193 def interfaces_added_handler(self, path, iprops, **kw):
194 for x in iprops.iterkeys():
195 if self.intf_match in x:
196 self.cache.add_item((path, kw['sender'], x))
197
198 def interfaces_removed_handler(self, path, interfaces, **kw):
199 for x in interfaces:
200 self.cache.remove_item((path, kw['sender'], x))
201
202 def process_new_owner(self, name):
203 # unique name
204 return self.discover([ Owner(name, self.bus) ])
205
206 def process_old_owner(self, name):
207 # unique name
208 self.cache.remove_bus(name)
209
210 def bus_handler(self, service, old, new):
211 if not self.discovery_done or \
212 self.name_match not in service:
213 return
214
215 if new:
216 self.process_new_owner(new)
217 if old:
218 self.process_old_owner(old)
219
220 def discover(self, owners = None):
221 discovery = not self.discovery_done
222 if not owners:
223 owners = self.bus.get_owners(self.name_match)
224 self.discovery_done = True
225 for o in owners:
226
227 # this only happens when an app
228 # grabs more than one well known name
229 if self.cache.has_bus(o.name):
230 continue
231
232 self.cache.add_items(o.discover(self.intf_match))
233
234 if discovery:
235 print "ObjectMapper discovery complete..."
236
237 @dbus.service.method(OpenBMCMapper.MAPPER_IFACE, 'sis', 'as')
238 def GetTreePaths(self, path, depth, match_type):
239 return self.cache.get_subtree_paths(path, depth, match_type)
240
241 @dbus.service.method(OpenBMCMapper.MAPPER_IFACE, 'sis', 'a{sa{sas}}')
242 def GetTree(self, path, depth, match_type):
243 return self.cache.get_subtree(path, depth, match_type)
244
245 @dbus.service.method(OpenBMCMapper.MAPPER_IFACE, 'asis', 'a{sa{sas}}')
246 def GetTrees(self, paths, depth, match_type):
247 values = {}
248 for x in paths:
249 values.update(self.GetTree(x, depth, match_type))
250 return values
251
252class IntrospectionParser:
253 def __init__(self, data):
254 self.data = data
255 self.cache = {}
256
257 def get_interfaces(self, match):
258 if 'interfaces' not in self.cache.keys():
259 self.cache['interfaces'] = [ x.attrib['name' ] \
260 for x in self.data.findall('interface') \
261 if match in x.attrib['name'] ]
262 return self.cache['interfaces']
263
264 def get_kids(self):
265 if 'kids' not in self.cache.keys():
266 self.cache['kids'] = [ x.attrib['name' ] \
267 for x in self.data.findall('node') ]
268 return self.cache['kids']
269
270 def recursive_binding(self):
271 return any('/' in s for s in self.get_kids())
272
273class Owner:
274 def __init__(self, name, bus):
275 self.name = name
276 self.bus = bus
277
278 def introspect(self, path):
279 try:
280 obj = self.bus.dbus.get_object(self.name, path)
281 iface = dbus.Interface(obj, 'org.freedesktop.DBus.Introspectable')
282 data = iface.Introspect()
283 except dbus.DBusException:
284 return None
285
286 return IntrospectionParser(ElementTree.fromstring(data))
287
288 def discover(self, match, path = '/'):
289 parser = self.introspect(path)
290 if not parser:
291 return []
292
293 items = []
294 for x in parser.get_interfaces(match):
295 items.append((path, self.name, x))
296
297 if path != '/':
298 path += '/'
299
300 recursive = parser.recursive_binding()
301 for k in parser.get_kids():
302 if recursive:
303 parser = self.introspect(path + k)
304 if not parser:
305 return []
306 for x in parser.get_interfaces(match):
307 items.append((path + k, self.name, x))
308 else:
309 items.extend(self.discover(match, path + k))
310
311 return items
312
313class BusWrapper:
314 def __init__(self, bus):
315 self.dbus = bus
316
317 def get_service_names(self, match):
318 # these are well known names
319 return [ x for x in self.dbus.list_names() \
320 if match in x and x != OpenBMCMapper.MAPPER_NAME ]
321
322 def get_owner_names(self, match):
323 # these are unique connection names
324 return list( set( [ self.dbus.get_name_owner(x) \
325 for x in self.get_service_names(match) ] ) )
326
327 def get_owners(self, match):
328 return [ Owner(x, self) \
329 for x in self.get_owner_names(match) ]
330
331if __name__ == '__main__':
332 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
333 bus = dbus.SystemBus()
334 s = dbus.service.BusName(OpenBMCMapper.MAPPER_NAME, bus)
335 o = ObjectMapper(BusWrapper(bus), OpenBMCMapper.MAPPER_PATH)
336 loop = gobject.MainLoop()
337
338 loop.run()