blob: 6af544e94bd1d1c2f29839cdd4bbc422819740ba [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)
40
Brad Bishop63f59a72016-07-25 12:05:57 -040041 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
Brad Bishop329472a2016-09-08 16:12:14 -040059 def _invoke_method(path, iface, method):
Brad Bishop63f59a72016-07-25 12:05:57 -040060 obj = _FindInterfaces._get_object(path)
61 if not obj:
62 return None
63
64 iface = dbus.Interface(obj, iface)
65 try:
Brad Bishop329472a2016-09-08 16:12:14 -040066 return method(iface)
Brad Bishop63f59a72016-07-25 12:05:57 -040067 except dbus.exceptions.DBusException, e:
68 if e.get_dbus_name() in [
69 obmc.dbuslib.enums.DBUS_UNKNOWN_SERVICE,
70 obmc.dbuslib.enums.DBUS_NO_REPLY]:
71 print "Warning: Introspection failure: " \
72 "service `%s` did not reply to "\
73 "method call on %s" % (service, path)
74 return None
75 raise
76
77 @staticmethod
78 def _introspect(path):
79 return _FindInterfaces._invoke_method(
80 path,
81 dbus.INTROSPECTABLE_IFACE,
Brad Bishop329472a2016-09-08 16:12:14 -040082 lambda x: x.Introspect())
Brad Bishop63f59a72016-07-25 12:05:57 -040083
84 @staticmethod
85 def _get_managed_objects(om):
86 return _FindInterfaces._invoke_method(
87 om,
88 dbus.BUS_DAEMON_IFACE + '.ObjectManager',
Brad Bishop329472a2016-09-08 16:12:14 -040089 lambda x: x.GetManagedObjects())
Brad Bishop63f59a72016-07-25 12:05:57 -040090
91 @staticmethod
92 def _to_path(elements):
93 return '/' + '/'.join(elements)
94
95 @staticmethod
96 def _to_path_elements(path):
97 return filter(bool, path.split('/'))
98
99 def __call__(self, path):
100 self.results = {}
101 self._find_interfaces(path)
102 return self.results
103
104 @staticmethod
105 def _match(iface):
106 return iface == dbus.BUS_DAEMON_IFACE + '.ObjectManager' \
Brad Bishopbd8aa052016-09-19 09:30:06 -0400107 or iface_match(iface)
Brad Bishop63f59a72016-07-25 12:05:57 -0400108
109 def _find_interfaces(self, path):
110 path_elements = self._to_path_elements(path)
111 path = self._to_path(path_elements)
112 data = self._introspect(path)
113 if data is None:
114 return
115
116 root = ET.fromstring(data)
117 ifaces = filter(
118 self._match,
119 [x.attrib.get('name') for x in root.findall('interface')])
120 self.results[path] = ifaces
121
122 if dbus.BUS_DAEMON_IFACE + '.ObjectManager' in ifaces:
123 objs = self._get_managed_objects(path)
124 for k, v in objs.iteritems():
125 self.results[k] = v
126 else:
127 children = filter(
128 bool,
129 [x.attrib.get('name') for x in root.findall('node')])
130 children = [
131 self._to_path(
132 path_elements + self._to_path_elements(x))
133 for x in sorted(children)]
134 for child in children:
135 if child not in self.results:
136 self._find_interfaces(child)
137
138 return _FindInterfaces()(path)
139
140
141class Association(dbus.service.Object):
142 def __init__(self, bus, path, endpoints):
Brad Bishop70dd5952016-09-08 22:33:33 -0400143 super(Association, self).__init__(conn=bus, object_path=path)
Brad Bishop63f59a72016-07-25 12:05:57 -0400144 self.endpoints = endpoints
145
146 def __getattr__(self, name):
147 if name == 'properties':
148 return {
149 obmc.dbuslib.enums.OBMC_ASSOC_IFACE: {
150 'endpoints': self.endpoints}}
151 return super(Association, self).__getattr__(name)
152
153 def emit_signal(self, old):
154 if old != self.endpoints:
155 self.PropertiesChanged(
156 obmc.dbuslib.enums.OBMC_ASSOC_IFACE,
157 {'endpoints': self.endpoints}, ['endpoints'])
158
159 def append(self, endpoints):
160 old = self.endpoints
161 self.endpoints = list(set(endpoints).union(self.endpoints))
162 self.emit_signal(old)
163
164 def remove(self, endpoints):
165 old = self.endpoints
166 self.endpoints = list(set(self.endpoints).difference(endpoints))
167 self.emit_signal(old)
168
169 @dbus.service.method(dbus.PROPERTIES_IFACE, 'ss', 'as')
170 def Get(self, interface_name, property_name):
171 if property_name != 'endpoints':
172 raise dbus.exceptions.DBusException(name=DBUS_UNKNOWN_PROPERTY)
173 return self.GetAll(interface_name)[property_name]
174
175 @dbus.service.method(dbus.PROPERTIES_IFACE, 's', 'a{sas}')
176 def GetAll(self, interface_name):
177 if interface_name != obmc.dbuslib.enums.OBMC_ASSOC_IFACE:
178 raise dbus.exceptions.DBusException(DBUS_UNKNOWN_INTERFACE)
179 return {'endpoints': self.endpoints}
180
181 @dbus.service.signal(
182 dbus.PROPERTIES_IFACE, signature='sa{sas}as')
183 def PropertiesChanged(
184 self, interface_name, changed_properties, invalidated_properties):
185 pass
186
187
188class Manager(obmc.dbuslib.bindings.DbusObjectManager):
189 def __init__(self, bus, path):
Brad Bishop70dd5952016-09-08 22:33:33 -0400190 super(Manager, self).__init__(conn=bus, object_path=path)
Brad Bishop63f59a72016-07-25 12:05:57 -0400191
192
193class ObjectMapper(dbus.service.Object):
194 def __init__(self, bus, path,
195 name_match=obmc.utils.misc.org_dot_openbmc_match,
196 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
200 self.name_match = name_match
201 self.intf_match = intf_match
202 self.tag_match = obmc.utils.misc.ListMatch(['children', 'interface'])
203 self.service = None
204 self.index = {}
205 self.manager = Manager(bus, obmc.dbuslib.bindings.OBJ_PREFIX)
206 self.unique = bus.get_unique_name()
207 self.bus_map = {}
208
209 gobject.idle_add(self.discover)
210 self.bus.add_signal_receiver(
211 self.bus_handler,
212 dbus_interface=dbus.BUS_DAEMON_IFACE,
213 signal_name='NameOwnerChanged')
214 self.bus.add_signal_receiver(
215 self.interfaces_added_handler,
216 dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager',
217 signal_name='InterfacesAdded',
218 sender_keyword='sender',
219 path_keyword='sender_path')
220 self.bus.add_signal_receiver(
221 self.interfaces_removed_handler,
222 dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager',
223 signal_name='InterfacesRemoved',
224 sender_keyword='sender',
225 path_keyword='sender_path')
226 self.bus.add_signal_receiver(
227 self.properties_changed_handler,
228 dbus_interface=dbus.PROPERTIES_IFACE,
229 signal_name='PropertiesChanged',
230 path_keyword='path',
231 sender_keyword='sender')
232
233 def bus_match(self, owner):
234 # Ignore my own signals
235 return owner != obmc.mapper.MAPPER_NAME and \
236 self.name_match(owner)
237
238 def discovery_pending(self):
239 return not bool(self.service)
240
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)
365 if not owners:
366 owned_names = [
367 x for x in self.bus.list_names() if self.bus_match(x)]
368 owners = [self.bus.get_name_owner(x) for x in owned_names]
369 owners = zip(owned_names, owners)
370 for owned_name, o in owners:
371 self.add_items(
372 o,
Brad Bishopbd8aa052016-09-19 09:30:06 -0400373 find_dbus_interfaces(
374 self.bus, o, '/', 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
396 return name is not None and \
397 self.bus_match(name)
398
399 def get_signal_interfaces(self, owner, interfaces):
400 filtered = []
401 if self.valid_signal(owner):
402 filtered = [str(x) for x in interfaces if self.intf_match(x)]
403
404 return filtered
405
406 @staticmethod
407 def interfaces_get(item, owner, default=[]):
408 return item.get(owner, default)
409
410 @staticmethod
411 def interfaces_append(item, owner, append):
412 interfaces = item.setdefault(owner, [])
413 item[owner] = list(set(append).union(interfaces))
414
415 def interfaces_remove(self, item, owner, remove, path):
416 interfaces = item.get(owner, [])
417 item[owner] = list(set(interfaces).difference(remove))
418
419 if not item[owner]:
420 # remove the owner if there aren't any interfaces left
421 del item[owner]
422
423 if item:
424 # other owners remain
425 return
426
427 if self.cache.get_children(path):
428 # there are still references to this path
429 # from objects further down the tree.
430 # mark it for removal if that changes
431 self.cache.demote(path)
432 else:
433 # delete the entire path if everything is gone
434 del self.cache[path]
435
436 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 's', 'a{sas}')
437 def GetObject(self, path):
438 o = self.cache_get(path)
439 if not o:
440 raise MapperNotFoundException(path)
441 return o
442
443 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 'si', 'as')
444 def GetSubTreePaths(self, path, depth):
445 try:
446 return self.cache.iterkeys(path, depth)
447 except KeyError:
448 raise MapperNotFoundException(path)
449
450 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 'si', 'a{sa{sas}}')
451 def GetSubTree(self, path, depth):
452 try:
453 return {x: y for x, y in self.cache.dataitems(path, depth)}
454 except KeyError:
455 raise MapperNotFoundException(path)
456
457 @staticmethod
458 def has_interfaces(item):
459 for owner in item.iterkeys():
460 if ObjectMapper.interfaces_get(item, owner):
461 return True
462 return False
463
464 @staticmethod
465 def is_association(interfaces):
466 return obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE in interfaces
467
468 def index_get(self, index, path, owners):
469 items = []
470 item = self.index.get(index, {})
471 item = item.get(path, {})
472 for o in owners:
473 items.extend(item.get(o, []))
474 return items
475
476 def index_append(self, index, path, owner, assoc):
477 item = self.index.setdefault(index, {})
478 item = item.setdefault(path, {})
479 item = item.setdefault(owner, [])
480 item.append(assoc)
481
482 def index_remove(self, index, path, owner, assoc):
483 index = self.index.get(index, {})
484 owners = index.get(path, {})
485 items = owners.get(owner, [])
486 if assoc in items:
487 items.remove(assoc)
488 if not items:
489 del owners[owner]
490 if not owners:
491 del index[path]
492
493 def dbus_get_associations(self, path, owner, obj):
494 iface = obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE
495 if 'associations' in obj[iface]:
496 return obj[iface]['associations']
497
498 # fallback to dbus
499 obj = self.bus.get_object(owner, path, introspect=False)
500 iface = dbus.Interface(obj, dbus.PROPERTIES_IFACE)
501 return [(str(f), str(r), str(e)) for f, r, e in iface.Get(
502 obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE,
503 'associations')]
504
505 def index_get_associations(self, path, owners=[], direction='forward'):
506 forward = 'forward' if direction == 'forward' else 'reverse'
507 reverse = 'reverse' if direction == 'forward' else 'forward'
508
509 associations = []
510 if not owners:
511 index = self.index.get(forward, {})
512 owners = index.get(path, {}).keys()
513
514 # f: forward
515 # r: reverse
516 for rassoc in self.index_get(forward, path, owners):
517 elements = rassoc.split('/')
518 rtype = ''.join(elements[-1:])
519 fendpoint = '/'.join(elements[:-1])
520 for fassoc in self.index_get(reverse, fendpoint, owners):
521 elements = fassoc.split('/')
522 ftype = ''.join(elements[-1:])
523 rendpoint = '/'.join(elements[:-1])
524 if rendpoint != path:
525 continue
526 associations.append((ftype, rtype, fendpoint))
527
528 return associations
529
530 def update_association(self, path, removed, added):
531 iface = obmc.dbuslib.enums.OBMC_ASSOC_IFACE
532 create = [] if self.manager.get(path, False) else [iface]
533
534 if added and create:
535 self.manager.add(
536 path, Association(self.bus, path, added))
537 elif added:
538 self.manager.get(path).append(added)
539
540 obj = self.manager.get(path, None)
541 if obj and removed:
542 obj.remove(removed)
543
544 if obj and not obj.endpoints:
545 self.manager.remove(path)
546
547 delete = [] if self.manager.get(path, False) else [iface]
548
549 if create != delete:
550 self.update_interfaces(
551 path, self.unique, delete, create)
552
553 def update_associations(
554 self, path, owner, old, new, created=[], destroyed=[]):
555 added = list(set(new).difference(old))
556 removed = list(set(old).difference(new))
557 for forward, reverse, endpoint in added:
558 # update the index
559 forward_path = str(path + '/' + forward)
560 reverse_path = str(endpoint + '/' + reverse)
561 self.index_append(
562 'forward', path, owner, reverse_path)
563 self.index_append(
564 'reverse', endpoint, owner, forward_path)
565
566 # create the association if the endpoint exists
567 if not self.cache_get(endpoint):
568 continue
569
570 self.update_association(forward_path, [], [endpoint])
571 self.update_association(reverse_path, [], [path])
572
573 for forward, reverse, endpoint in removed:
574 # update the index
575 forward_path = str(path + '/' + forward)
576 reverse_path = str(endpoint + '/' + reverse)
577 self.index_remove(
578 'forward', path, owner, reverse_path)
579 self.index_remove(
580 'reverse', endpoint, owner, forward_path)
581
582 # destroy the association if it exists
583 self.update_association(forward_path, [endpoint], [])
584 self.update_association(reverse_path, [path], [])
585
586 # If the associations interface endpoint comes
587 # or goes create or destroy the appropriate
588 # associations
589 for path in created:
590 for forward, reverse, endpoint in \
591 self.index_get_associations(path, direction='reverse'):
592 forward_path = str(path + '/' + forward)
593 reverse_path = str(endpoint + '/' + reverse)
594 self.update_association(forward_path, [], [endpoint])
595 self.update_association(reverse_path, [], [path])
596
597 for path in destroyed:
598 for forward, reverse, endpoint in \
599 self.index_get_associations(path, direction='reverse'):
600 forward_path = str(path + '/' + forward)
601 reverse_path = str(endpoint + '/' + reverse)
602 self.update_association(forward_path, [endpoint], [])
603 self.update_association(reverse_path, [path], [])
604
605 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 's', 'a{sa{sas}}')
606 def GetAncestors(self, path):
607 elements = filter(bool, path.split('/'))
608 paths = []
609 objs = {}
610 while elements:
611 elements.pop()
612 paths.append('/' + '/'.join(elements))
613 if path != '/':
614 paths.append('/')
615
616 for path in paths:
617 obj = self.cache_get(path)
618 if not obj:
619 continue
620 objs[path] = obj
621
622 return objs
623
624
625def server_main():
626 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
627 bus = dbus.SystemBus()
628 o = ObjectMapper(bus, obmc.mapper.MAPPER_PATH)
629 loop = gobject.MainLoop()
630
631 loop.run()