blob: 22e549718919700ee1a768e6e96621e82938e44e [file] [log] [blame]
Brad Bishop63f59a72016-07-25 12:05:57 -04001# Contributors Listed Below - COPYRIGHT 2016
2# [+] International Business Machines Corp.
3#
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14# implied. See the License for the specific language governing
15# permissions and limitations under the License.
16
17import dbus
18import dbus.service
19import dbus.exceptions
20import dbus.mainloop.glib
21import gobject
22import xml.etree.ElementTree as ET
23import obmc.utils.pathtree
24import obmc.utils.misc
25import obmc.mapper
26import obmc.dbuslib.bindings
27import obmc.dbuslib.enums
28
29
30class MapperNotFoundException(dbus.exceptions.DBusException):
31 _dbus_error_name = obmc.mapper.MAPPER_NOT_FOUND
32
33 def __init__(self, path):
34 super(MapperNotFoundException, self).__init__(
35 "path or object not found: %s" % path)
36
37
Brad Bishopbd8aa052016-09-19 09:30:06 -040038def find_dbus_interfaces(conn, service, path, **kw):
39 iface_match = kw.pop('iface_match', bool)
Brad Bishop6a0320b2016-09-19 11:03:06 -040040 subtree_match = kw.pop('subtree_match', bool)
Brad Bishopbd8aa052016-09-19 09:30:06 -040041
Brad Bishop63f59a72016-07-25 12:05:57 -040042 class _FindInterfaces(object):
43 def __init__(self):
44 self.results = {}
45
46 @staticmethod
47 def _get_object(path):
48 try:
49 return conn.get_object(service, path, introspect=False)
50 except dbus.exceptions.DBusException, e:
51 if e.get_dbus_name() in [
52 obmc.dbuslib.enums.DBUS_UNKNOWN_SERVICE,
53 obmc.dbuslib.enums.DBUS_NO_REPLY]:
54 print "Warning: Introspection failure: " \
55 "service `%s` is not running" % (service)
56 return None
57 raise
58
59 @staticmethod
Brad Bishop329472a2016-09-08 16:12:14 -040060 def _invoke_method(path, iface, method):
Brad Bishop63f59a72016-07-25 12:05:57 -040061 obj = _FindInterfaces._get_object(path)
62 if not obj:
63 return None
64
65 iface = dbus.Interface(obj, iface)
66 try:
Brad Bishop329472a2016-09-08 16:12:14 -040067 return method(iface)
Brad Bishop63f59a72016-07-25 12:05:57 -040068 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,
Brad Bishop329472a2016-09-08 16:12:14 -040083 lambda x: x.Introspect())
Brad Bishop63f59a72016-07-25 12:05:57 -040084
85 @staticmethod
86 def _get_managed_objects(om):
87 return _FindInterfaces._invoke_method(
88 om,
89 dbus.BUS_DAEMON_IFACE + '.ObjectManager',
Brad Bishop329472a2016-09-08 16:12:14 -040090 lambda x: x.GetManagedObjects())
Brad Bishop63f59a72016-07-25 12:05:57 -040091
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' \
Brad Bishopbd8aa052016-09-19 09:30:06 -0400108 or iface_match(iface)
Brad Bishop63f59a72016-07-25 12:05:57 -0400109
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)]
Brad Bishop6a0320b2016-09-19 11:03:06 -0400135 for child in filter(subtree_match, children):
Brad Bishop63f59a72016-07-25 12:05:57 -0400136 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):
Brad Bishop70dd5952016-09-08 22:33:33 -0400144 super(Association, self).__init__(conn=bus, object_path=path)
Brad Bishop63f59a72016-07-25 12:05:57 -0400145 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):
Brad Bishop70dd5952016-09-08 22:33:33 -0400191 super(Manager, self).__init__(conn=bus, object_path=path)
Brad Bishop63f59a72016-07-25 12:05:57 -0400192
193
194class ObjectMapper(dbus.service.Object):
195 def __init__(self, bus, path,
196 name_match=obmc.utils.misc.org_dot_openbmc_match,
197 intf_match=obmc.utils.misc.org_dot_openbmc_match):
198 super(ObjectMapper, self).__init__(bus, path)
199 self.cache = obmc.utils.pathtree.PathTree()
200 self.bus = bus
201 self.name_match = name_match
202 self.intf_match = intf_match
203 self.tag_match = obmc.utils.misc.ListMatch(['children', 'interface'])
204 self.service = None
205 self.index = {}
206 self.manager = Manager(bus, obmc.dbuslib.bindings.OBJ_PREFIX)
207 self.unique = bus.get_unique_name()
208 self.bus_map = {}
209
210 gobject.idle_add(self.discover)
211 self.bus.add_signal_receiver(
212 self.bus_handler,
213 dbus_interface=dbus.BUS_DAEMON_IFACE,
214 signal_name='NameOwnerChanged')
215 self.bus.add_signal_receiver(
216 self.interfaces_added_handler,
217 dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager',
218 signal_name='InterfacesAdded',
219 sender_keyword='sender',
220 path_keyword='sender_path')
221 self.bus.add_signal_receiver(
222 self.interfaces_removed_handler,
223 dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager',
224 signal_name='InterfacesRemoved',
225 sender_keyword='sender',
226 path_keyword='sender_path')
227 self.bus.add_signal_receiver(
228 self.properties_changed_handler,
229 dbus_interface=dbus.PROPERTIES_IFACE,
230 signal_name='PropertiesChanged',
231 path_keyword='path',
232 sender_keyword='sender')
233
234 def bus_match(self, owner):
235 # Ignore my own signals
236 return owner != obmc.mapper.MAPPER_NAME and \
237 self.name_match(owner)
238
239 def discovery_pending(self):
240 return not bool(self.service)
241
242 def cache_get(self, path):
243 cache_entry = self.cache.get(path, {})
244 if cache_entry is None:
245 # hide path elements without any interfaces
246 cache_entry = {}
247 return cache_entry
248
249 def add_new_objmgr(self, path, owner):
250 # We don't get a signal for the ObjectManager
251 # interface itself, so if we see a signal from
252 # make sure its in our cache, and add it if not.
253 cache_entry = self.cache_get(path)
254 old = self.interfaces_get(cache_entry, owner)
255 new = list(set(old).union([dbus.BUS_DAEMON_IFACE + '.ObjectManager']))
256 self.update_interfaces(path, owner, old, new)
257
258 def interfaces_added_handler(self, path, iprops, **kw):
259 path = str(path)
260 owner = str(kw['sender'])
261 interfaces = self.get_signal_interfaces(owner, iprops.iterkeys())
262 if interfaces:
263 self.add_new_objmgr(str(kw['sender_path']), owner)
264 cache_entry = self.cache_get(path)
265 old = self.interfaces_get(cache_entry, owner)
266 new = list(set(interfaces).union(old))
267 self.update_interfaces(path, owner, old, new)
268
269 def interfaces_removed_handler(self, path, interfaces, **kw):
270 path = str(path)
271 owner = str(kw['sender'])
272 interfaces = self.get_signal_interfaces(owner, interfaces)
273 if interfaces:
274 self.add_new_objmgr(str(kw['sender_path']), owner)
275 cache_entry = self.cache_get(path)
276 old = self.interfaces_get(cache_entry, owner)
277 new = list(set(old).difference(interfaces))
278 self.update_interfaces(path, owner, old, new)
279
280 def properties_changed_handler(self, interface, new, old, **kw):
281 owner = str(kw['sender'])
282 path = str(kw['path'])
283 interfaces = self.get_signal_interfaces(owner, [interface])
284 if not self.is_association(interfaces):
285 return
286 associations = new.get('associations', None)
287 if associations is None:
288 return
289
290 associations = [
291 (str(x), str(y), str(z)) for x, y, z in associations]
292 self.update_associations(
293 path, owner,
294 self.index_get_associations(path, [owner]),
295 associations)
296
297 def process_new_owner(self, owned_name, owner):
298 # unique name
299 try:
300 return self.discover([(owned_name, owner)])
301 except dbus.exceptions.DBusException, e:
302 if obmc.dbuslib.enums.DBUS_UNKNOWN_SERVICE \
303 not in e.get_dbus_name():
304 raise
305
306 def process_old_owner(self, owned_name, owner):
307 if owner in self.bus_map:
308 del self.bus_map[owner]
309
310 for path, item in self.cache.dataitems():
311 old = self.interfaces_get(item, owner)
312 # remove all interfaces for this service
313 self.update_interfaces(
314 path, owner, old=old, new=[])
315
316 def bus_handler(self, owned_name, old, new):
317 valid = False
318 if not obmc.dbuslib.bindings.is_unique(owned_name):
319 valid = self.valid_signal(owned_name)
320
321 if valid and new:
322 self.process_new_owner(owned_name, new)
323 if valid and old:
324 self.process_old_owner(owned_name, old)
325
326 def update_interfaces(self, path, owner, old, new):
327 # __xx -> intf list
328 # xx -> intf dict
329 if isinstance(old, dict):
330 __old = old.keys()
331 else:
332 __old = old
333 old = {x: {} for x in old}
334 if isinstance(new, dict):
335 __new = new.keys()
336 else:
337 __new = new
338 new = {x: {} for x in new}
339
340 cache_entry = self.cache.setdefault(path, {})
341 created = [] if self.has_interfaces(cache_entry) else [path]
342 added = list(set(__new).difference(__old))
343 removed = list(set(__old).difference(__new))
344 self.interfaces_append(cache_entry, owner, added)
345 self.interfaces_remove(cache_entry, owner, removed, path)
346 destroyed = [] if self.has_interfaces(cache_entry) else [path]
347
348 # react to anything that requires association updates
349 new_assoc = []
350 old_assoc = []
351 if self.is_association(added):
352 new_assoc = self.dbus_get_associations(path, owner, new)
353 if self.is_association(removed):
354 old_assoc = self.index_get_associations(path, [owner])
355 self.update_associations(
356 path, owner, old_assoc, new_assoc, created, destroyed)
357
358 def add_items(self, owner, bus_items):
359 for path, items in bus_items.iteritems():
360 self.update_interfaces(path, str(owner), old=[], new=items)
361
362 def discover(self, owners=[]):
363 def match(iface):
364 return iface == dbus.BUS_DAEMON_IFACE + '.ObjectManager' or \
365 self.intf_match(iface)
Brad Bishop6a0320b2016-09-19 11:03:06 -0400366
367 subtree_match = lambda x: obmc.utils.misc.org_dot_openbmc_match(
368 x, sep='/', prefix='/')
369
Brad Bishop63f59a72016-07-25 12:05:57 -0400370 if not owners:
371 owned_names = [
372 x for x in self.bus.list_names() if self.bus_match(x)]
373 owners = [self.bus.get_name_owner(x) for x in owned_names]
374 owners = zip(owned_names, owners)
375 for owned_name, o in owners:
376 self.add_items(
377 o,
Brad Bishopbd8aa052016-09-19 09:30:06 -0400378 find_dbus_interfaces(
Brad Bishop6a0320b2016-09-19 11:03:06 -0400379 self.bus, o, '/',
380 subtree_match=subtree_match,
381 iface_match=self.intf_match))
Brad Bishop63f59a72016-07-25 12:05:57 -0400382 self.bus_map[o] = owned_name
383
384 if self.discovery_pending():
385 # add my object mananger instance
386 self.bus_map[self.unique] = obmc.mapper.MAPPER_NAME
387 self.add_items(
388 self.unique,
389 {obmc.dbuslib.bindings.OBJ_PREFIX:
Brad Bishop22ff24a2016-07-25 12:26:15 -0400390 [dbus.BUS_DAEMON_IFACE + '.ObjectManager']})
Brad Bishop63f59a72016-07-25 12:05:57 -0400391
392 print "ObjectMapper discovery complete..."
393 self.service = dbus.service.BusName(
394 obmc.mapper.MAPPER_NAME, self.bus)
395
396 def valid_signal(self, name):
397 if self.discovery_pending():
398 return False
399
400 if obmc.dbuslib.bindings.is_unique(name):
401 name = self.bus_map.get(name)
402
403 return name is not None and \
404 self.bus_match(name)
405
406 def get_signal_interfaces(self, owner, interfaces):
407 filtered = []
408 if self.valid_signal(owner):
409 filtered = [str(x) for x in interfaces if self.intf_match(x)]
410
411 return filtered
412
413 @staticmethod
414 def interfaces_get(item, owner, default=[]):
415 return item.get(owner, default)
416
417 @staticmethod
418 def interfaces_append(item, owner, append):
419 interfaces = item.setdefault(owner, [])
420 item[owner] = list(set(append).union(interfaces))
421
422 def interfaces_remove(self, item, owner, remove, path):
423 interfaces = item.get(owner, [])
424 item[owner] = list(set(interfaces).difference(remove))
425
426 if not item[owner]:
427 # remove the owner if there aren't any interfaces left
428 del item[owner]
429
430 if item:
431 # other owners remain
432 return
433
434 if self.cache.get_children(path):
435 # there are still references to this path
436 # from objects further down the tree.
437 # mark it for removal if that changes
438 self.cache.demote(path)
439 else:
440 # delete the entire path if everything is gone
441 del self.cache[path]
442
443 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 's', 'a{sas}')
444 def GetObject(self, path):
445 o = self.cache_get(path)
446 if not o:
447 raise MapperNotFoundException(path)
448 return o
449
450 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 'si', 'as')
451 def GetSubTreePaths(self, path, depth):
452 try:
453 return self.cache.iterkeys(path, depth)
454 except KeyError:
455 raise MapperNotFoundException(path)
456
457 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 'si', 'a{sa{sas}}')
458 def GetSubTree(self, path, depth):
459 try:
460 return {x: y for x, y in self.cache.dataitems(path, depth)}
461 except KeyError:
462 raise MapperNotFoundException(path)
463
464 @staticmethod
465 def has_interfaces(item):
466 for owner in item.iterkeys():
467 if ObjectMapper.interfaces_get(item, owner):
468 return True
469 return False
470
471 @staticmethod
472 def is_association(interfaces):
473 return obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE in interfaces
474
475 def index_get(self, index, path, owners):
476 items = []
477 item = self.index.get(index, {})
478 item = item.get(path, {})
479 for o in owners:
480 items.extend(item.get(o, []))
481 return items
482
483 def index_append(self, index, path, owner, assoc):
484 item = self.index.setdefault(index, {})
485 item = item.setdefault(path, {})
486 item = item.setdefault(owner, [])
487 item.append(assoc)
488
489 def index_remove(self, index, path, owner, assoc):
490 index = self.index.get(index, {})
491 owners = index.get(path, {})
492 items = owners.get(owner, [])
493 if assoc in items:
494 items.remove(assoc)
495 if not items:
496 del owners[owner]
497 if not owners:
498 del index[path]
499
500 def dbus_get_associations(self, path, owner, obj):
501 iface = obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE
502 if 'associations' in obj[iface]:
503 return obj[iface]['associations']
504
505 # fallback to dbus
506 obj = self.bus.get_object(owner, path, introspect=False)
507 iface = dbus.Interface(obj, dbus.PROPERTIES_IFACE)
508 return [(str(f), str(r), str(e)) for f, r, e in iface.Get(
509 obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE,
510 'associations')]
511
512 def index_get_associations(self, path, owners=[], direction='forward'):
513 forward = 'forward' if direction == 'forward' else 'reverse'
514 reverse = 'reverse' if direction == 'forward' else 'forward'
515
516 associations = []
517 if not owners:
518 index = self.index.get(forward, {})
519 owners = index.get(path, {}).keys()
520
521 # f: forward
522 # r: reverse
523 for rassoc in self.index_get(forward, path, owners):
524 elements = rassoc.split('/')
525 rtype = ''.join(elements[-1:])
526 fendpoint = '/'.join(elements[:-1])
527 for fassoc in self.index_get(reverse, fendpoint, owners):
528 elements = fassoc.split('/')
529 ftype = ''.join(elements[-1:])
530 rendpoint = '/'.join(elements[:-1])
531 if rendpoint != path:
532 continue
533 associations.append((ftype, rtype, fendpoint))
534
535 return associations
536
537 def update_association(self, path, removed, added):
538 iface = obmc.dbuslib.enums.OBMC_ASSOC_IFACE
539 create = [] if self.manager.get(path, False) else [iface]
540
541 if added and create:
542 self.manager.add(
543 path, Association(self.bus, path, added))
544 elif added:
545 self.manager.get(path).append(added)
546
547 obj = self.manager.get(path, None)
548 if obj and removed:
549 obj.remove(removed)
550
551 if obj and not obj.endpoints:
552 self.manager.remove(path)
553
554 delete = [] if self.manager.get(path, False) else [iface]
555
556 if create != delete:
557 self.update_interfaces(
558 path, self.unique, delete, create)
559
560 def update_associations(
561 self, path, owner, old, new, created=[], destroyed=[]):
562 added = list(set(new).difference(old))
563 removed = list(set(old).difference(new))
564 for forward, reverse, endpoint in added:
565 # update the index
566 forward_path = str(path + '/' + forward)
567 reverse_path = str(endpoint + '/' + reverse)
568 self.index_append(
569 'forward', path, owner, reverse_path)
570 self.index_append(
571 'reverse', endpoint, owner, forward_path)
572
573 # create the association if the endpoint exists
574 if not self.cache_get(endpoint):
575 continue
576
577 self.update_association(forward_path, [], [endpoint])
578 self.update_association(reverse_path, [], [path])
579
580 for forward, reverse, endpoint in removed:
581 # update the index
582 forward_path = str(path + '/' + forward)
583 reverse_path = str(endpoint + '/' + reverse)
584 self.index_remove(
585 'forward', path, owner, reverse_path)
586 self.index_remove(
587 'reverse', endpoint, owner, forward_path)
588
589 # destroy the association if it exists
590 self.update_association(forward_path, [endpoint], [])
591 self.update_association(reverse_path, [path], [])
592
593 # If the associations interface endpoint comes
594 # or goes create or destroy the appropriate
595 # associations
596 for path in created:
597 for forward, reverse, endpoint in \
598 self.index_get_associations(path, direction='reverse'):
599 forward_path = str(path + '/' + forward)
600 reverse_path = str(endpoint + '/' + reverse)
601 self.update_association(forward_path, [], [endpoint])
602 self.update_association(reverse_path, [], [path])
603
604 for path in destroyed:
605 for forward, reverse, endpoint in \
606 self.index_get_associations(path, direction='reverse'):
607 forward_path = str(path + '/' + forward)
608 reverse_path = str(endpoint + '/' + reverse)
609 self.update_association(forward_path, [endpoint], [])
610 self.update_association(reverse_path, [path], [])
611
612 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 's', 'a{sa{sas}}')
613 def GetAncestors(self, path):
614 elements = filter(bool, path.split('/'))
615 paths = []
616 objs = {}
617 while elements:
618 elements.pop()
619 paths.append('/' + '/'.join(elements))
620 if path != '/':
621 paths.append('/')
622
623 for path in paths:
624 obj = self.cache_get(path)
625 if not obj:
626 continue
627 objs[path] = obj
628
629 return objs
630
631
632def server_main():
633 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
634 bus = dbus.SystemBus()
635 o = ObjectMapper(bus, obmc.mapper.MAPPER_PATH)
636 loop = gobject.MainLoop()
637
638 loop.run()