blob: 3e73f8be0672a59b0d461c06172d4cdb30a17018 [file] [log] [blame]
Brad Bishop63f59a72016-07-25 12:05:57 -04001#!/usr/bin/env python
2
3# Contributors Listed Below - COPYRIGHT 2016
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 dbus
20import dbus.service
21import dbus.exceptions
22import dbus.mainloop.glib
23import gobject
24import xml.etree.ElementTree as ET
25import obmc.utils.pathtree
26import obmc.utils.misc
27import obmc.mapper
28import obmc.dbuslib.bindings
29import obmc.dbuslib.enums
30
31
32class MapperNotFoundException(dbus.exceptions.DBusException):
33 _dbus_error_name = obmc.mapper.MAPPER_NOT_FOUND
34
35 def __init__(self, path):
36 super(MapperNotFoundException, self).__init__(
37 "path or object not found: %s" % path)
38
39
40def find_dbus_interfaces(conn, service, path, match):
41 class _FindInterfaces(object):
42 def __init__(self):
43 self.results = {}
44
45 @staticmethod
46 def _get_object(path):
47 try:
48 return conn.get_object(service, path, introspect=False)
49 except dbus.exceptions.DBusException, e:
50 if e.get_dbus_name() in [
51 obmc.dbuslib.enums.DBUS_UNKNOWN_SERVICE,
52 obmc.dbuslib.enums.DBUS_NO_REPLY]:
53 print "Warning: Introspection failure: " \
54 "service `%s` is not running" % (service)
55 return None
56 raise
57
58 @staticmethod
59 def _invoke_method(path, iface, method, *args):
60 obj = _FindInterfaces._get_object(path)
61 if not obj:
62 return None
63
64 iface = dbus.Interface(obj, iface)
65 try:
66 f = getattr(iface, method)
67 return f(*args)
68 except dbus.exceptions.DBusException, e:
69 if e.get_dbus_name() in [
70 obmc.dbuslib.enums.DBUS_UNKNOWN_SERVICE,
71 obmc.dbuslib.enums.DBUS_NO_REPLY]:
72 print "Warning: Introspection failure: " \
73 "service `%s` did not reply to "\
74 "method call on %s" % (service, path)
75 return None
76 raise
77
78 @staticmethod
79 def _introspect(path):
80 return _FindInterfaces._invoke_method(
81 path,
82 dbus.INTROSPECTABLE_IFACE,
83 'Introspect')
84
85 @staticmethod
86 def _get_managed_objects(om):
87 return _FindInterfaces._invoke_method(
88 om,
89 dbus.BUS_DAEMON_IFACE + '.ObjectManager',
90 'GetManagedObjects')
91
92 @staticmethod
93 def _to_path(elements):
94 return '/' + '/'.join(elements)
95
96 @staticmethod
97 def _to_path_elements(path):
98 return filter(bool, path.split('/'))
99
100 def __call__(self, path):
101 self.results = {}
102 self._find_interfaces(path)
103 return self.results
104
105 @staticmethod
106 def _match(iface):
107 return iface == dbus.BUS_DAEMON_IFACE + '.ObjectManager' \
108 or match(iface)
109
110 def _find_interfaces(self, path):
111 path_elements = self._to_path_elements(path)
112 path = self._to_path(path_elements)
113 data = self._introspect(path)
114 if data is None:
115 return
116
117 root = ET.fromstring(data)
118 ifaces = filter(
119 self._match,
120 [x.attrib.get('name') for x in root.findall('interface')])
121 self.results[path] = ifaces
122
123 if dbus.BUS_DAEMON_IFACE + '.ObjectManager' in ifaces:
124 objs = self._get_managed_objects(path)
125 for k, v in objs.iteritems():
126 self.results[k] = v
127 else:
128 children = filter(
129 bool,
130 [x.attrib.get('name') for x in root.findall('node')])
131 children = [
132 self._to_path(
133 path_elements + self._to_path_elements(x))
134 for x in sorted(children)]
135 for child in children:
136 if child not in self.results:
137 self._find_interfaces(child)
138
139 return _FindInterfaces()(path)
140
141
142class Association(dbus.service.Object):
143 def __init__(self, bus, path, endpoints):
144 super(Association, self).__init__(bus, path)
145 self.endpoints = endpoints
146
147 def __getattr__(self, name):
148 if name == 'properties':
149 return {
150 obmc.dbuslib.enums.OBMC_ASSOC_IFACE: {
151 'endpoints': self.endpoints}}
152 return super(Association, self).__getattr__(name)
153
154 def emit_signal(self, old):
155 if old != self.endpoints:
156 self.PropertiesChanged(
157 obmc.dbuslib.enums.OBMC_ASSOC_IFACE,
158 {'endpoints': self.endpoints}, ['endpoints'])
159
160 def append(self, endpoints):
161 old = self.endpoints
162 self.endpoints = list(set(endpoints).union(self.endpoints))
163 self.emit_signal(old)
164
165 def remove(self, endpoints):
166 old = self.endpoints
167 self.endpoints = list(set(self.endpoints).difference(endpoints))
168 self.emit_signal(old)
169
170 @dbus.service.method(dbus.PROPERTIES_IFACE, 'ss', 'as')
171 def Get(self, interface_name, property_name):
172 if property_name != 'endpoints':
173 raise dbus.exceptions.DBusException(name=DBUS_UNKNOWN_PROPERTY)
174 return self.GetAll(interface_name)[property_name]
175
176 @dbus.service.method(dbus.PROPERTIES_IFACE, 's', 'a{sas}')
177 def GetAll(self, interface_name):
178 if interface_name != obmc.dbuslib.enums.OBMC_ASSOC_IFACE:
179 raise dbus.exceptions.DBusException(DBUS_UNKNOWN_INTERFACE)
180 return {'endpoints': self.endpoints}
181
182 @dbus.service.signal(
183 dbus.PROPERTIES_IFACE, signature='sa{sas}as')
184 def PropertiesChanged(
185 self, interface_name, changed_properties, invalidated_properties):
186 pass
187
188
189class Manager(obmc.dbuslib.bindings.DbusObjectManager):
190 def __init__(self, bus, path):
191 obmc.dbuslib.bindings.DbusObjectManager.__init__(self)
192 dbus.service.Object.__init__(self, bus, path)
193
194
195class ObjectMapper(dbus.service.Object):
196 def __init__(self, bus, path,
197 name_match=obmc.utils.misc.org_dot_openbmc_match,
198 intf_match=obmc.utils.misc.org_dot_openbmc_match):
199 super(ObjectMapper, self).__init__(bus, path)
200 self.cache = obmc.utils.pathtree.PathTree()
201 self.bus = bus
202 self.name_match = name_match
203 self.intf_match = intf_match
204 self.tag_match = obmc.utils.misc.ListMatch(['children', 'interface'])
205 self.service = None
206 self.index = {}
207 self.manager = Manager(bus, obmc.dbuslib.bindings.OBJ_PREFIX)
208 self.unique = bus.get_unique_name()
209 self.bus_map = {}
210
211 gobject.idle_add(self.discover)
212 self.bus.add_signal_receiver(
213 self.bus_handler,
214 dbus_interface=dbus.BUS_DAEMON_IFACE,
215 signal_name='NameOwnerChanged')
216 self.bus.add_signal_receiver(
217 self.interfaces_added_handler,
218 dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager',
219 signal_name='InterfacesAdded',
220 sender_keyword='sender',
221 path_keyword='sender_path')
222 self.bus.add_signal_receiver(
223 self.interfaces_removed_handler,
224 dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager',
225 signal_name='InterfacesRemoved',
226 sender_keyword='sender',
227 path_keyword='sender_path')
228 self.bus.add_signal_receiver(
229 self.properties_changed_handler,
230 dbus_interface=dbus.PROPERTIES_IFACE,
231 signal_name='PropertiesChanged',
232 path_keyword='path',
233 sender_keyword='sender')
234
235 def bus_match(self, owner):
236 # Ignore my own signals
237 return owner != obmc.mapper.MAPPER_NAME and \
238 self.name_match(owner)
239
240 def discovery_pending(self):
241 return not bool(self.service)
242
243 def cache_get(self, path):
244 cache_entry = self.cache.get(path, {})
245 if cache_entry is None:
246 # hide path elements without any interfaces
247 cache_entry = {}
248 return cache_entry
249
250 def add_new_objmgr(self, path, owner):
251 # We don't get a signal for the ObjectManager
252 # interface itself, so if we see a signal from
253 # make sure its in our cache, and add it if not.
254 cache_entry = self.cache_get(path)
255 old = self.interfaces_get(cache_entry, owner)
256 new = list(set(old).union([dbus.BUS_DAEMON_IFACE + '.ObjectManager']))
257 self.update_interfaces(path, owner, old, new)
258
259 def interfaces_added_handler(self, path, iprops, **kw):
260 path = str(path)
261 owner = str(kw['sender'])
262 interfaces = self.get_signal_interfaces(owner, iprops.iterkeys())
263 if interfaces:
264 self.add_new_objmgr(str(kw['sender_path']), owner)
265 cache_entry = self.cache_get(path)
266 old = self.interfaces_get(cache_entry, owner)
267 new = list(set(interfaces).union(old))
268 self.update_interfaces(path, owner, old, new)
269
270 def interfaces_removed_handler(self, path, interfaces, **kw):
271 path = str(path)
272 owner = str(kw['sender'])
273 interfaces = self.get_signal_interfaces(owner, interfaces)
274 if interfaces:
275 self.add_new_objmgr(str(kw['sender_path']), owner)
276 cache_entry = self.cache_get(path)
277 old = self.interfaces_get(cache_entry, owner)
278 new = list(set(old).difference(interfaces))
279 self.update_interfaces(path, owner, old, new)
280
281 def properties_changed_handler(self, interface, new, old, **kw):
282 owner = str(kw['sender'])
283 path = str(kw['path'])
284 interfaces = self.get_signal_interfaces(owner, [interface])
285 if not self.is_association(interfaces):
286 return
287 associations = new.get('associations', None)
288 if associations is None:
289 return
290
291 associations = [
292 (str(x), str(y), str(z)) for x, y, z in associations]
293 self.update_associations(
294 path, owner,
295 self.index_get_associations(path, [owner]),
296 associations)
297
298 def process_new_owner(self, owned_name, owner):
299 # unique name
300 try:
301 return self.discover([(owned_name, owner)])
302 except dbus.exceptions.DBusException, e:
303 if obmc.dbuslib.enums.DBUS_UNKNOWN_SERVICE \
304 not in e.get_dbus_name():
305 raise
306
307 def process_old_owner(self, owned_name, owner):
308 if owner in self.bus_map:
309 del self.bus_map[owner]
310
311 for path, item in self.cache.dataitems():
312 old = self.interfaces_get(item, owner)
313 # remove all interfaces for this service
314 self.update_interfaces(
315 path, owner, old=old, new=[])
316
317 def bus_handler(self, owned_name, old, new):
318 valid = False
319 if not obmc.dbuslib.bindings.is_unique(owned_name):
320 valid = self.valid_signal(owned_name)
321
322 if valid and new:
323 self.process_new_owner(owned_name, new)
324 if valid and old:
325 self.process_old_owner(owned_name, old)
326
327 def update_interfaces(self, path, owner, old, new):
328 # __xx -> intf list
329 # xx -> intf dict
330 if isinstance(old, dict):
331 __old = old.keys()
332 else:
333 __old = old
334 old = {x: {} for x in old}
335 if isinstance(new, dict):
336 __new = new.keys()
337 else:
338 __new = new
339 new = {x: {} for x in new}
340
341 cache_entry = self.cache.setdefault(path, {})
342 created = [] if self.has_interfaces(cache_entry) else [path]
343 added = list(set(__new).difference(__old))
344 removed = list(set(__old).difference(__new))
345 self.interfaces_append(cache_entry, owner, added)
346 self.interfaces_remove(cache_entry, owner, removed, path)
347 destroyed = [] if self.has_interfaces(cache_entry) else [path]
348
349 # react to anything that requires association updates
350 new_assoc = []
351 old_assoc = []
352 if self.is_association(added):
353 new_assoc = self.dbus_get_associations(path, owner, new)
354 if self.is_association(removed):
355 old_assoc = self.index_get_associations(path, [owner])
356 self.update_associations(
357 path, owner, old_assoc, new_assoc, created, destroyed)
358
359 def add_items(self, owner, bus_items):
360 for path, items in bus_items.iteritems():
361 self.update_interfaces(path, str(owner), old=[], new=items)
362
363 def discover(self, owners=[]):
364 def match(iface):
365 return iface == dbus.BUS_DAEMON_IFACE + '.ObjectManager' or \
366 self.intf_match(iface)
367 if not owners:
368 owned_names = [
369 x for x in self.bus.list_names() if self.bus_match(x)]
370 owners = [self.bus.get_name_owner(x) for x in owned_names]
371 owners = zip(owned_names, owners)
372 for owned_name, o in owners:
373 self.add_items(
374 o,
375 find_dbus_interfaces(self.bus, o, '/', self.intf_match))
376 self.bus_map[o] = owned_name
377
378 if self.discovery_pending():
379 # add my object mananger instance
380 self.bus_map[self.unique] = obmc.mapper.MAPPER_NAME
381 self.add_items(
382 self.unique,
383 {obmc.dbuslib.bindings.OBJ_PREFIX:
Brad Bishop22ff24a2016-07-25 12:26:15 -0400384 [dbus.BUS_DAEMON_IFACE + '.ObjectManager']})
Brad Bishop63f59a72016-07-25 12:05:57 -0400385
386 print "ObjectMapper discovery complete..."
387 self.service = dbus.service.BusName(
388 obmc.mapper.MAPPER_NAME, self.bus)
389
390 def valid_signal(self, name):
391 if self.discovery_pending():
392 return False
393
394 if obmc.dbuslib.bindings.is_unique(name):
395 name = self.bus_map.get(name)
396
397 return name is not None and \
398 self.bus_match(name)
399
400 def get_signal_interfaces(self, owner, interfaces):
401 filtered = []
402 if self.valid_signal(owner):
403 filtered = [str(x) for x in interfaces if self.intf_match(x)]
404
405 return filtered
406
407 @staticmethod
408 def interfaces_get(item, owner, default=[]):
409 return item.get(owner, default)
410
411 @staticmethod
412 def interfaces_append(item, owner, append):
413 interfaces = item.setdefault(owner, [])
414 item[owner] = list(set(append).union(interfaces))
415
416 def interfaces_remove(self, item, owner, remove, path):
417 interfaces = item.get(owner, [])
418 item[owner] = list(set(interfaces).difference(remove))
419
420 if not item[owner]:
421 # remove the owner if there aren't any interfaces left
422 del item[owner]
423
424 if item:
425 # other owners remain
426 return
427
428 if self.cache.get_children(path):
429 # there are still references to this path
430 # from objects further down the tree.
431 # mark it for removal if that changes
432 self.cache.demote(path)
433 else:
434 # delete the entire path if everything is gone
435 del self.cache[path]
436
437 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 's', 'a{sas}')
438 def GetObject(self, path):
439 o = self.cache_get(path)
440 if not o:
441 raise MapperNotFoundException(path)
442 return o
443
444 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 'si', 'as')
445 def GetSubTreePaths(self, path, depth):
446 try:
447 return self.cache.iterkeys(path, depth)
448 except KeyError:
449 raise MapperNotFoundException(path)
450
451 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 'si', 'a{sa{sas}}')
452 def GetSubTree(self, path, depth):
453 try:
454 return {x: y for x, y in self.cache.dataitems(path, depth)}
455 except KeyError:
456 raise MapperNotFoundException(path)
457
458 @staticmethod
459 def has_interfaces(item):
460 for owner in item.iterkeys():
461 if ObjectMapper.interfaces_get(item, owner):
462 return True
463 return False
464
465 @staticmethod
466 def is_association(interfaces):
467 return obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE in interfaces
468
469 def index_get(self, index, path, owners):
470 items = []
471 item = self.index.get(index, {})
472 item = item.get(path, {})
473 for o in owners:
474 items.extend(item.get(o, []))
475 return items
476
477 def index_append(self, index, path, owner, assoc):
478 item = self.index.setdefault(index, {})
479 item = item.setdefault(path, {})
480 item = item.setdefault(owner, [])
481 item.append(assoc)
482
483 def index_remove(self, index, path, owner, assoc):
484 index = self.index.get(index, {})
485 owners = index.get(path, {})
486 items = owners.get(owner, [])
487 if assoc in items:
488 items.remove(assoc)
489 if not items:
490 del owners[owner]
491 if not owners:
492 del index[path]
493
494 def dbus_get_associations(self, path, owner, obj):
495 iface = obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE
496 if 'associations' in obj[iface]:
497 return obj[iface]['associations']
498
499 # fallback to dbus
500 obj = self.bus.get_object(owner, path, introspect=False)
501 iface = dbus.Interface(obj, dbus.PROPERTIES_IFACE)
502 return [(str(f), str(r), str(e)) for f, r, e in iface.Get(
503 obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE,
504 'associations')]
505
506 def index_get_associations(self, path, owners=[], direction='forward'):
507 forward = 'forward' if direction == 'forward' else 'reverse'
508 reverse = 'reverse' if direction == 'forward' else 'forward'
509
510 associations = []
511 if not owners:
512 index = self.index.get(forward, {})
513 owners = index.get(path, {}).keys()
514
515 # f: forward
516 # r: reverse
517 for rassoc in self.index_get(forward, path, owners):
518 elements = rassoc.split('/')
519 rtype = ''.join(elements[-1:])
520 fendpoint = '/'.join(elements[:-1])
521 for fassoc in self.index_get(reverse, fendpoint, owners):
522 elements = fassoc.split('/')
523 ftype = ''.join(elements[-1:])
524 rendpoint = '/'.join(elements[:-1])
525 if rendpoint != path:
526 continue
527 associations.append((ftype, rtype, fendpoint))
528
529 return associations
530
531 def update_association(self, path, removed, added):
532 iface = obmc.dbuslib.enums.OBMC_ASSOC_IFACE
533 create = [] if self.manager.get(path, False) else [iface]
534
535 if added and create:
536 self.manager.add(
537 path, Association(self.bus, path, added))
538 elif added:
539 self.manager.get(path).append(added)
540
541 obj = self.manager.get(path, None)
542 if obj and removed:
543 obj.remove(removed)
544
545 if obj and not obj.endpoints:
546 self.manager.remove(path)
547
548 delete = [] if self.manager.get(path, False) else [iface]
549
550 if create != delete:
551 self.update_interfaces(
552 path, self.unique, delete, create)
553
554 def update_associations(
555 self, path, owner, old, new, created=[], destroyed=[]):
556 added = list(set(new).difference(old))
557 removed = list(set(old).difference(new))
558 for forward, reverse, endpoint in added:
559 # update the index
560 forward_path = str(path + '/' + forward)
561 reverse_path = str(endpoint + '/' + reverse)
562 self.index_append(
563 'forward', path, owner, reverse_path)
564 self.index_append(
565 'reverse', endpoint, owner, forward_path)
566
567 # create the association if the endpoint exists
568 if not self.cache_get(endpoint):
569 continue
570
571 self.update_association(forward_path, [], [endpoint])
572 self.update_association(reverse_path, [], [path])
573
574 for forward, reverse, endpoint in removed:
575 # update the index
576 forward_path = str(path + '/' + forward)
577 reverse_path = str(endpoint + '/' + reverse)
578 self.index_remove(
579 'forward', path, owner, reverse_path)
580 self.index_remove(
581 'reverse', endpoint, owner, forward_path)
582
583 # destroy the association if it exists
584 self.update_association(forward_path, [endpoint], [])
585 self.update_association(reverse_path, [path], [])
586
587 # If the associations interface endpoint comes
588 # or goes create or destroy the appropriate
589 # associations
590 for path in created:
591 for forward, reverse, endpoint in \
592 self.index_get_associations(path, direction='reverse'):
593 forward_path = str(path + '/' + forward)
594 reverse_path = str(endpoint + '/' + reverse)
595 self.update_association(forward_path, [], [endpoint])
596 self.update_association(reverse_path, [], [path])
597
598 for path in destroyed:
599 for forward, reverse, endpoint in \
600 self.index_get_associations(path, direction='reverse'):
601 forward_path = str(path + '/' + forward)
602 reverse_path = str(endpoint + '/' + reverse)
603 self.update_association(forward_path, [endpoint], [])
604 self.update_association(reverse_path, [path], [])
605
606 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 's', 'a{sa{sas}}')
607 def GetAncestors(self, path):
608 elements = filter(bool, path.split('/'))
609 paths = []
610 objs = {}
611 while elements:
612 elements.pop()
613 paths.append('/' + '/'.join(elements))
614 if path != '/':
615 paths.append('/')
616
617 for path in paths:
618 obj = self.cache_get(path)
619 if not obj:
620 continue
621 objs[path] = obj
622
623 return objs
624
625
626def server_main():
627 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
628 bus = dbus.SystemBus()
629 o = ObjectMapper(bus, obmc.mapper.MAPPER_PATH)
630 loop = gobject.MainLoop()
631
632 loop.run()