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