blob: 800880da8c4a585d4f48f4382ffd506ac4283c0d [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,
Brad Bishop63f59a72016-07-25 12:05:57 -0400196 intf_match=obmc.utils.misc.org_dot_openbmc_match):
197 super(ObjectMapper, self).__init__(bus, path)
198 self.cache = obmc.utils.pathtree.PathTree()
199 self.bus = bus
Brad Bishop63f59a72016-07-25 12:05:57 -0400200 self.intf_match = intf_match
Brad Bishop63f59a72016-07-25 12:05:57 -0400201 self.service = None
202 self.index = {}
203 self.manager = Manager(bus, obmc.dbuslib.bindings.OBJ_PREFIX)
204 self.unique = bus.get_unique_name()
205 self.bus_map = {}
206
207 gobject.idle_add(self.discover)
208 self.bus.add_signal_receiver(
209 self.bus_handler,
210 dbus_interface=dbus.BUS_DAEMON_IFACE,
211 signal_name='NameOwnerChanged')
212 self.bus.add_signal_receiver(
213 self.interfaces_added_handler,
214 dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager',
215 signal_name='InterfacesAdded',
216 sender_keyword='sender',
217 path_keyword='sender_path')
218 self.bus.add_signal_receiver(
219 self.interfaces_removed_handler,
220 dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager',
221 signal_name='InterfacesRemoved',
222 sender_keyword='sender',
223 path_keyword='sender_path')
224 self.bus.add_signal_receiver(
225 self.properties_changed_handler,
226 dbus_interface=dbus.PROPERTIES_IFACE,
227 signal_name='PropertiesChanged',
228 path_keyword='path',
229 sender_keyword='sender')
230
Brad Bishop63f59a72016-07-25 12:05:57 -0400231 def discovery_pending(self):
232 return not bool(self.service)
233
234 def cache_get(self, path):
235 cache_entry = self.cache.get(path, {})
236 if cache_entry is None:
237 # hide path elements without any interfaces
238 cache_entry = {}
239 return cache_entry
240
241 def add_new_objmgr(self, path, owner):
242 # We don't get a signal for the ObjectManager
243 # interface itself, so if we see a signal from
244 # make sure its in our cache, and add it if not.
245 cache_entry = self.cache_get(path)
246 old = self.interfaces_get(cache_entry, owner)
247 new = list(set(old).union([dbus.BUS_DAEMON_IFACE + '.ObjectManager']))
248 self.update_interfaces(path, owner, old, new)
249
250 def interfaces_added_handler(self, path, iprops, **kw):
251 path = str(path)
252 owner = str(kw['sender'])
253 interfaces = self.get_signal_interfaces(owner, iprops.iterkeys())
254 if interfaces:
255 self.add_new_objmgr(str(kw['sender_path']), owner)
256 cache_entry = self.cache_get(path)
257 old = self.interfaces_get(cache_entry, owner)
258 new = list(set(interfaces).union(old))
259 self.update_interfaces(path, owner, old, new)
260
261 def interfaces_removed_handler(self, path, interfaces, **kw):
262 path = str(path)
263 owner = str(kw['sender'])
264 interfaces = self.get_signal_interfaces(owner, interfaces)
265 if interfaces:
266 self.add_new_objmgr(str(kw['sender_path']), owner)
267 cache_entry = self.cache_get(path)
268 old = self.interfaces_get(cache_entry, owner)
269 new = list(set(old).difference(interfaces))
270 self.update_interfaces(path, owner, old, new)
271
272 def properties_changed_handler(self, interface, new, old, **kw):
273 owner = str(kw['sender'])
274 path = str(kw['path'])
275 interfaces = self.get_signal_interfaces(owner, [interface])
276 if not self.is_association(interfaces):
277 return
278 associations = new.get('associations', None)
279 if associations is None:
280 return
281
282 associations = [
283 (str(x), str(y), str(z)) for x, y, z in associations]
284 self.update_associations(
285 path, owner,
286 self.index_get_associations(path, [owner]),
287 associations)
288
289 def process_new_owner(self, owned_name, owner):
290 # unique name
291 try:
292 return self.discover([(owned_name, owner)])
293 except dbus.exceptions.DBusException, e:
294 if obmc.dbuslib.enums.DBUS_UNKNOWN_SERVICE \
295 not in e.get_dbus_name():
296 raise
297
298 def process_old_owner(self, owned_name, owner):
299 if owner in self.bus_map:
300 del self.bus_map[owner]
301
302 for path, item in self.cache.dataitems():
303 old = self.interfaces_get(item, owner)
304 # remove all interfaces for this service
305 self.update_interfaces(
306 path, owner, old=old, new=[])
307
308 def bus_handler(self, owned_name, old, new):
309 valid = False
310 if not obmc.dbuslib.bindings.is_unique(owned_name):
311 valid = self.valid_signal(owned_name)
312
313 if valid and new:
314 self.process_new_owner(owned_name, new)
315 if valid and old:
316 self.process_old_owner(owned_name, old)
317
318 def update_interfaces(self, path, owner, old, new):
319 # __xx -> intf list
320 # xx -> intf dict
321 if isinstance(old, dict):
322 __old = old.keys()
323 else:
324 __old = old
325 old = {x: {} for x in old}
326 if isinstance(new, dict):
327 __new = new.keys()
328 else:
329 __new = new
330 new = {x: {} for x in new}
331
332 cache_entry = self.cache.setdefault(path, {})
333 created = [] if self.has_interfaces(cache_entry) else [path]
334 added = list(set(__new).difference(__old))
335 removed = list(set(__old).difference(__new))
336 self.interfaces_append(cache_entry, owner, added)
337 self.interfaces_remove(cache_entry, owner, removed, path)
338 destroyed = [] if self.has_interfaces(cache_entry) else [path]
339
340 # react to anything that requires association updates
341 new_assoc = []
342 old_assoc = []
343 if self.is_association(added):
344 new_assoc = self.dbus_get_associations(path, owner, new)
345 if self.is_association(removed):
346 old_assoc = self.index_get_associations(path, [owner])
347 self.update_associations(
348 path, owner, old_assoc, new_assoc, created, destroyed)
349
350 def add_items(self, owner, bus_items):
351 for path, items in bus_items.iteritems():
352 self.update_interfaces(path, str(owner), old=[], new=items)
353
354 def discover(self, owners=[]):
355 def match(iface):
356 return iface == dbus.BUS_DAEMON_IFACE + '.ObjectManager' or \
357 self.intf_match(iface)
Brad Bishop6a0320b2016-09-19 11:03:06 -0400358
359 subtree_match = lambda x: obmc.utils.misc.org_dot_openbmc_match(
360 x, sep='/', prefix='/')
361
Brad Bishop63f59a72016-07-25 12:05:57 -0400362 if not owners:
Brad Bishopd0b8e392016-09-19 11:24:45 -0400363 owned_names = filter(
364 lambda x: not obmc.dbuslib.bindings.is_unique(x),
365 self.bus.list_names())
Brad Bishop63f59a72016-07-25 12:05:57 -0400366 owners = [self.bus.get_name_owner(x) for x in owned_names]
367 owners = zip(owned_names, owners)
368 for owned_name, o in owners:
369 self.add_items(
370 o,
Brad Bishopbd8aa052016-09-19 09:30:06 -0400371 find_dbus_interfaces(
Brad Bishop6a0320b2016-09-19 11:03:06 -0400372 self.bus, o, '/',
373 subtree_match=subtree_match,
374 iface_match=self.intf_match))
Brad Bishop63f59a72016-07-25 12:05:57 -0400375 self.bus_map[o] = owned_name
376
377 if self.discovery_pending():
378 # add my object mananger instance
379 self.bus_map[self.unique] = obmc.mapper.MAPPER_NAME
380 self.add_items(
381 self.unique,
382 {obmc.dbuslib.bindings.OBJ_PREFIX:
Brad Bishop22ff24a2016-07-25 12:26:15 -0400383 [dbus.BUS_DAEMON_IFACE + '.ObjectManager']})
Brad Bishop63f59a72016-07-25 12:05:57 -0400384
385 print "ObjectMapper discovery complete..."
386 self.service = dbus.service.BusName(
387 obmc.mapper.MAPPER_NAME, self.bus)
388
389 def valid_signal(self, name):
390 if self.discovery_pending():
391 return False
392
393 if obmc.dbuslib.bindings.is_unique(name):
394 name = self.bus_map.get(name)
395
Brad Bishopd0b8e392016-09-19 11:24:45 -0400396 return name is not None and name is not obmc.mapper.MAPPER_NAME
Brad Bishop63f59a72016-07-25 12:05:57 -0400397
398 def get_signal_interfaces(self, owner, interfaces):
399 filtered = []
400 if self.valid_signal(owner):
401 filtered = [str(x) for x in interfaces if self.intf_match(x)]
402
403 return filtered
404
405 @staticmethod
406 def interfaces_get(item, owner, default=[]):
407 return item.get(owner, default)
408
409 @staticmethod
410 def interfaces_append(item, owner, append):
411 interfaces = item.setdefault(owner, [])
412 item[owner] = list(set(append).union(interfaces))
413
414 def interfaces_remove(self, item, owner, remove, path):
415 interfaces = item.get(owner, [])
416 item[owner] = list(set(interfaces).difference(remove))
417
418 if not item[owner]:
419 # remove the owner if there aren't any interfaces left
420 del item[owner]
421
422 if item:
423 # other owners remain
424 return
425
426 if self.cache.get_children(path):
427 # there are still references to this path
428 # from objects further down the tree.
429 # mark it for removal if that changes
430 self.cache.demote(path)
431 else:
432 # delete the entire path if everything is gone
433 del self.cache[path]
434
435 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 's', 'a{sas}')
436 def GetObject(self, path):
437 o = self.cache_get(path)
438 if not o:
439 raise MapperNotFoundException(path)
440 return o
441
442 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 'si', 'as')
443 def GetSubTreePaths(self, path, depth):
444 try:
445 return self.cache.iterkeys(path, depth)
446 except KeyError:
447 raise MapperNotFoundException(path)
448
449 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 'si', 'a{sa{sas}}')
450 def GetSubTree(self, path, depth):
451 try:
452 return {x: y for x, y in self.cache.dataitems(path, depth)}
453 except KeyError:
454 raise MapperNotFoundException(path)
455
456 @staticmethod
457 def has_interfaces(item):
458 for owner in item.iterkeys():
459 if ObjectMapper.interfaces_get(item, owner):
460 return True
461 return False
462
463 @staticmethod
464 def is_association(interfaces):
465 return obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE in interfaces
466
467 def index_get(self, index, path, owners):
468 items = []
469 item = self.index.get(index, {})
470 item = item.get(path, {})
471 for o in owners:
472 items.extend(item.get(o, []))
473 return items
474
475 def index_append(self, index, path, owner, assoc):
476 item = self.index.setdefault(index, {})
477 item = item.setdefault(path, {})
478 item = item.setdefault(owner, [])
479 item.append(assoc)
480
481 def index_remove(self, index, path, owner, assoc):
482 index = self.index.get(index, {})
483 owners = index.get(path, {})
484 items = owners.get(owner, [])
485 if assoc in items:
486 items.remove(assoc)
487 if not items:
488 del owners[owner]
489 if not owners:
490 del index[path]
491
492 def dbus_get_associations(self, path, owner, obj):
493 iface = obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE
494 if 'associations' in obj[iface]:
495 return obj[iface]['associations']
496
497 # fallback to dbus
498 obj = self.bus.get_object(owner, path, introspect=False)
499 iface = dbus.Interface(obj, dbus.PROPERTIES_IFACE)
500 return [(str(f), str(r), str(e)) for f, r, e in iface.Get(
501 obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE,
502 'associations')]
503
504 def index_get_associations(self, path, owners=[], direction='forward'):
505 forward = 'forward' if direction == 'forward' else 'reverse'
506 reverse = 'reverse' if direction == 'forward' else 'forward'
507
508 associations = []
509 if not owners:
510 index = self.index.get(forward, {})
511 owners = index.get(path, {}).keys()
512
513 # f: forward
514 # r: reverse
515 for rassoc in self.index_get(forward, path, owners):
516 elements = rassoc.split('/')
517 rtype = ''.join(elements[-1:])
518 fendpoint = '/'.join(elements[:-1])
519 for fassoc in self.index_get(reverse, fendpoint, owners):
520 elements = fassoc.split('/')
521 ftype = ''.join(elements[-1:])
522 rendpoint = '/'.join(elements[:-1])
523 if rendpoint != path:
524 continue
525 associations.append((ftype, rtype, fendpoint))
526
527 return associations
528
529 def update_association(self, path, removed, added):
530 iface = obmc.dbuslib.enums.OBMC_ASSOC_IFACE
531 create = [] if self.manager.get(path, False) else [iface]
532
533 if added and create:
534 self.manager.add(
535 path, Association(self.bus, path, added))
536 elif added:
537 self.manager.get(path).append(added)
538
539 obj = self.manager.get(path, None)
540 if obj and removed:
541 obj.remove(removed)
542
543 if obj and not obj.endpoints:
544 self.manager.remove(path)
545
546 delete = [] if self.manager.get(path, False) else [iface]
547
548 if create != delete:
549 self.update_interfaces(
550 path, self.unique, delete, create)
551
552 def update_associations(
553 self, path, owner, old, new, created=[], destroyed=[]):
554 added = list(set(new).difference(old))
555 removed = list(set(old).difference(new))
556 for forward, reverse, endpoint in added:
557 # update the index
558 forward_path = str(path + '/' + forward)
559 reverse_path = str(endpoint + '/' + reverse)
560 self.index_append(
561 'forward', path, owner, reverse_path)
562 self.index_append(
563 'reverse', endpoint, owner, forward_path)
564
565 # create the association if the endpoint exists
566 if not self.cache_get(endpoint):
567 continue
568
569 self.update_association(forward_path, [], [endpoint])
570 self.update_association(reverse_path, [], [path])
571
572 for forward, reverse, endpoint in removed:
573 # update the index
574 forward_path = str(path + '/' + forward)
575 reverse_path = str(endpoint + '/' + reverse)
576 self.index_remove(
577 'forward', path, owner, reverse_path)
578 self.index_remove(
579 'reverse', endpoint, owner, forward_path)
580
581 # destroy the association if it exists
582 self.update_association(forward_path, [endpoint], [])
583 self.update_association(reverse_path, [path], [])
584
585 # If the associations interface endpoint comes
586 # or goes create or destroy the appropriate
587 # associations
588 for path in created:
589 for forward, reverse, endpoint in \
590 self.index_get_associations(path, direction='reverse'):
591 forward_path = str(path + '/' + forward)
592 reverse_path = str(endpoint + '/' + reverse)
593 self.update_association(forward_path, [], [endpoint])
594 self.update_association(reverse_path, [], [path])
595
596 for path in destroyed:
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 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 's', 'a{sa{sas}}')
605 def GetAncestors(self, path):
606 elements = filter(bool, path.split('/'))
607 paths = []
608 objs = {}
609 while elements:
610 elements.pop()
611 paths.append('/' + '/'.join(elements))
612 if path != '/':
613 paths.append('/')
614
615 for path in paths:
616 obj = self.cache_get(path)
617 if not obj:
618 continue
619 objs[path] = obj
620
621 return objs
622
623
624def server_main():
625 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
626 bus = dbus.SystemBus()
627 o = ObjectMapper(bus, obmc.mapper.MAPPER_PATH)
628 loop = gobject.MainLoop()
629
630 loop.run()