blob: 890e5468a49f6e5afab15cb569fed2d6d467fef9 [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
Brad Bishop86398d22015-10-28 21:58:31 -0400204 return self.discover([ IntrospectionParser(name, self.bus.dbus) ])
Brad Bishop732c6db2015-10-19 14:49:21 -0400205
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
Brad Bishop86398d22015-10-28 21:58:31 -0400220 def add_match_interfaces(self, owner, path, interfaces):
221 for x in interfaces:
222 if self.intf_match not in x:
223 continue
224
225 self.cache.add_item((path, owner, x))
226
227 def add_match_items(self, owner, bus_items):
228 for x,y in bus_items.iteritems():
229 self.add_match_interfaces(owner, x, y['interfaces'])
230
Brad Bishop732c6db2015-10-19 14:49:21 -0400231 def discover(self, owners = None):
232 discovery = not self.discovery_done
233 if not owners:
234 owners = self.bus.get_owners(self.name_match)
235 self.discovery_done = True
236 for o in owners:
237
238 # this only happens when an app
239 # grabs more than one well known name
240 if self.cache.has_bus(o.name):
241 continue
242
Brad Bishop86398d22015-10-28 21:58:31 -0400243 self.add_match_items(o.name, o.introspect())
Brad Bishop732c6db2015-10-19 14:49:21 -0400244
245 if discovery:
246 print "ObjectMapper discovery complete..."
247
248 @dbus.service.method(OpenBMCMapper.MAPPER_IFACE, 'sis', 'as')
249 def GetTreePaths(self, path, depth, match_type):
250 return self.cache.get_subtree_paths(path, depth, match_type)
251
252 @dbus.service.method(OpenBMCMapper.MAPPER_IFACE, 'sis', 'a{sa{sas}}')
253 def GetTree(self, path, depth, match_type):
254 return self.cache.get_subtree(path, depth, match_type)
255
256 @dbus.service.method(OpenBMCMapper.MAPPER_IFACE, 'asis', 'a{sa{sas}}')
257 def GetTrees(self, paths, depth, match_type):
258 values = {}
259 for x in paths:
260 values.update(self.GetTree(x, depth, match_type))
261 return values
262
Brad Bishop86398d22015-10-28 21:58:31 -0400263class IntrospectionNodeParser:
Brad Bishop732c6db2015-10-19 14:49:21 -0400264 def __init__(self, data):
265 self.data = data
266 self.cache = {}
267
Brad Bishop86398d22015-10-28 21:58:31 -0400268 def parse_args(self):
269 return [ x.attrib for x in self.data.findall('arg') ]
Brad Bishop732c6db2015-10-19 14:49:21 -0400270
Brad Bishop86398d22015-10-28 21:58:31 -0400271 def parse_children(self):
272 return [ x.attrib['name'] for x in self.data.findall('node') ]
273
274 def parse_method_or_signal(self):
275 name = self.data.attrib['name']
276 return name, self.parse_args()
277
278 def parse_interface(self):
279 iface = {}
280 iface['method'] = {}
281 iface['signal'] = {}
282 name = self.data.attrib['name']
283
284 for node in self.data:
285 p = IntrospectionNodeParser(node)
286 if node.tag not in ['method', 'signal']:
287 continue
288 n, element = p.parse_method_or_signal()
289 iface[node.tag][n] = element
290
291 return name, iface
292
293 def parse_node(self):
294 if self.cache:
295 return self.cache
296
297 self.cache['interfaces'] = {}
298 self.cache['children'] = []
299
300 for node in self.data:
301 p = IntrospectionNodeParser(node)
302 if node.tag == 'interface':
303 name, ifaces = p.parse_interface()
304 self.cache['interfaces'][name] = ifaces
305 elif node.tag == 'node':
306 self.cache['children'] = self.parse_children()
307
308 return self.cache
309
310 def get_interfaces(self):
311 return self.parse_node()['interfaces']
312
313 def get_children(self):
314 return self.parse_node()['children']
Brad Bishop732c6db2015-10-19 14:49:21 -0400315
316 def recursive_binding(self):
Brad Bishop86398d22015-10-28 21:58:31 -0400317 return any('/' in s for s in self.get_children())
Brad Bishop732c6db2015-10-19 14:49:21 -0400318
Brad Bishop86398d22015-10-28 21:58:31 -0400319class IntrospectionParser:
Brad Bishop732c6db2015-10-19 14:49:21 -0400320 def __init__(self, name, bus):
321 self.name = name
322 self.bus = bus
323
Brad Bishop86398d22015-10-28 21:58:31 -0400324 def _introspect(self, path):
Brad Bishop732c6db2015-10-19 14:49:21 -0400325 try:
Brad Bishop86398d22015-10-28 21:58:31 -0400326 obj = self.bus.get_object(self.name, path)
327 iface = dbus.Interface(obj, dbus.BUS_DAEMON_IFACE + '.Introspectable')
Brad Bishop732c6db2015-10-19 14:49:21 -0400328 data = iface.Introspect()
329 except dbus.DBusException:
330 return None
331
Brad Bishop86398d22015-10-28 21:58:31 -0400332 return IntrospectionNodeParser(ElementTree.fromstring(data))
Brad Bishop732c6db2015-10-19 14:49:21 -0400333
Brad Bishop86398d22015-10-28 21:58:31 -0400334 def _discover_flat(self, path, parser):
335 items = {}
336 interfaces = parser.get_interfaces().keys()
337 if interfaces:
338 items[path] = {}
339 items[path]['interfaces'] = interfaces
340
341 return items
342
343 def introspect(self, path = '/', parser = None):
344 items = {}
Brad Bishop732c6db2015-10-19 14:49:21 -0400345 if not parser:
Brad Bishop86398d22015-10-28 21:58:31 -0400346 parser = self._introspect(path)
347 if not parser:
348 return {}
349 items.update(self._discover_flat(path, parser))
Brad Bishop732c6db2015-10-19 14:49:21 -0400350
351 if path != '/':
352 path += '/'
353
Brad Bishop86398d22015-10-28 21:58:31 -0400354 if parser.recursive_binding():
355 callback = self._discover_flat
356 else:
357 callback = self.introspect
358
359 for k in parser.get_children():
360 parser = self._introspect(path + k)
361 if not parser:
362 continue
363 items.update(callback(path + k, parser))
Brad Bishop732c6db2015-10-19 14:49:21 -0400364
365 return items
366
367class BusWrapper:
368 def __init__(self, bus):
369 self.dbus = bus
370
371 def get_service_names(self, match):
372 # these are well known names
373 return [ x for x in self.dbus.list_names() \
374 if match in x and x != OpenBMCMapper.MAPPER_NAME ]
375
376 def get_owner_names(self, match):
377 # these are unique connection names
378 return list( set( [ self.dbus.get_name_owner(x) \
379 for x in self.get_service_names(match) ] ) )
380
381 def get_owners(self, match):
Brad Bishop86398d22015-10-28 21:58:31 -0400382 return [ IntrospectionParser(x, self.dbus) \
Brad Bishop732c6db2015-10-19 14:49:21 -0400383 for x in self.get_owner_names(match) ]
384
385if __name__ == '__main__':
386 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
387 bus = dbus.SystemBus()
388 s = dbus.service.BusName(OpenBMCMapper.MAPPER_NAME, bus)
389 o = ObjectMapper(BusWrapper(bus), OpenBMCMapper.MAPPER_PATH)
390 loop = gobject.MainLoop()
391
392 loop.run()