blob: d4d4a33665a33e8da922625c08e6c9f3925af7c9 [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
38def find_dbus_interfaces(conn, service, path, match):
39 class _FindInterfaces(object):
40 def __init__(self):
41 self.results = {}
42
43 @staticmethod
44 def _get_object(path):
45 try:
46 return conn.get_object(service, path, introspect=False)
47 except dbus.exceptions.DBusException, e:
48 if e.get_dbus_name() in [
49 obmc.dbuslib.enums.DBUS_UNKNOWN_SERVICE,
50 obmc.dbuslib.enums.DBUS_NO_REPLY]:
51 print "Warning: Introspection failure: " \
52 "service `%s` is not running" % (service)
53 return None
54 raise
55
56 @staticmethod
57 def _invoke_method(path, iface, method, *args):
58 obj = _FindInterfaces._get_object(path)
59 if not obj:
60 return None
61
62 iface = dbus.Interface(obj, iface)
63 try:
64 f = getattr(iface, method)
65 return f(*args)
66 except dbus.exceptions.DBusException, e:
67 if e.get_dbus_name() in [
68 obmc.dbuslib.enums.DBUS_UNKNOWN_SERVICE,
69 obmc.dbuslib.enums.DBUS_NO_REPLY]:
70 print "Warning: Introspection failure: " \
71 "service `%s` did not reply to "\
72 "method call on %s" % (service, path)
73 return None
74 raise
75
76 @staticmethod
77 def _introspect(path):
78 return _FindInterfaces._invoke_method(
79 path,
80 dbus.INTROSPECTABLE_IFACE,
81 'Introspect')
82
83 @staticmethod
84 def _get_managed_objects(om):
85 return _FindInterfaces._invoke_method(
86 om,
87 dbus.BUS_DAEMON_IFACE + '.ObjectManager',
88 'GetManagedObjects')
89
90 @staticmethod
91 def _to_path(elements):
92 return '/' + '/'.join(elements)
93
94 @staticmethod
95 def _to_path_elements(path):
96 return filter(bool, path.split('/'))
97
98 def __call__(self, path):
99 self.results = {}
100 self._find_interfaces(path)
101 return self.results
102
103 @staticmethod
104 def _match(iface):
105 return iface == dbus.BUS_DAEMON_IFACE + '.ObjectManager' \
106 or match(iface)
107
108 def _find_interfaces(self, path):
109 path_elements = self._to_path_elements(path)
110 path = self._to_path(path_elements)
111 data = self._introspect(path)
112 if data is None:
113 return
114
115 root = ET.fromstring(data)
116 ifaces = filter(
117 self._match,
118 [x.attrib.get('name') for x in root.findall('interface')])
119 self.results[path] = ifaces
120
121 if dbus.BUS_DAEMON_IFACE + '.ObjectManager' in ifaces:
122 objs = self._get_managed_objects(path)
123 for k, v in objs.iteritems():
124 self.results[k] = v
125 else:
126 children = filter(
127 bool,
128 [x.attrib.get('name') for x in root.findall('node')])
129 children = [
130 self._to_path(
131 path_elements + self._to_path_elements(x))
132 for x in sorted(children)]
133 for child in children:
134 if child not in self.results:
135 self._find_interfaces(child)
136
137 return _FindInterfaces()(path)
138
139
140class Association(dbus.service.Object):
141 def __init__(self, bus, path, endpoints):
142 super(Association, self).__init__(bus, path)
143 self.endpoints = endpoints
144
145 def __getattr__(self, name):
146 if name == 'properties':
147 return {
148 obmc.dbuslib.enums.OBMC_ASSOC_IFACE: {
149 'endpoints': self.endpoints}}
150 return super(Association, self).__getattr__(name)
151
152 def emit_signal(self, old):
153 if old != self.endpoints:
154 self.PropertiesChanged(
155 obmc.dbuslib.enums.OBMC_ASSOC_IFACE,
156 {'endpoints': self.endpoints}, ['endpoints'])
157
158 def append(self, endpoints):
159 old = self.endpoints
160 self.endpoints = list(set(endpoints).union(self.endpoints))
161 self.emit_signal(old)
162
163 def remove(self, endpoints):
164 old = self.endpoints
165 self.endpoints = list(set(self.endpoints).difference(endpoints))
166 self.emit_signal(old)
167
168 @dbus.service.method(dbus.PROPERTIES_IFACE, 'ss', 'as')
169 def Get(self, interface_name, property_name):
170 if property_name != 'endpoints':
171 raise dbus.exceptions.DBusException(name=DBUS_UNKNOWN_PROPERTY)
172 return self.GetAll(interface_name)[property_name]
173
174 @dbus.service.method(dbus.PROPERTIES_IFACE, 's', 'a{sas}')
175 def GetAll(self, interface_name):
176 if interface_name != obmc.dbuslib.enums.OBMC_ASSOC_IFACE:
177 raise dbus.exceptions.DBusException(DBUS_UNKNOWN_INTERFACE)
178 return {'endpoints': self.endpoints}
179
180 @dbus.service.signal(
181 dbus.PROPERTIES_IFACE, signature='sa{sas}as')
182 def PropertiesChanged(
183 self, interface_name, changed_properties, invalidated_properties):
184 pass
185
186
187class Manager(obmc.dbuslib.bindings.DbusObjectManager):
188 def __init__(self, bus, path):
189 obmc.dbuslib.bindings.DbusObjectManager.__init__(self)
190 dbus.service.Object.__init__(self, bus, path)
191
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,
373 find_dbus_interfaces(self.bus, o, '/', self.intf_match))
374 self.bus_map[o] = owned_name
375
376 if self.discovery_pending():
377 # add my object mananger instance
378 self.bus_map[self.unique] = obmc.mapper.MAPPER_NAME
379 self.add_items(
380 self.unique,
381 {obmc.dbuslib.bindings.OBJ_PREFIX:
Brad Bishop22ff24a2016-07-25 12:26:15 -0400382 [dbus.BUS_DAEMON_IFACE + '.ObjectManager']})
Brad Bishop63f59a72016-07-25 12:05:57 -0400383
384 print "ObjectMapper discovery complete..."
385 self.service = dbus.service.BusName(
386 obmc.mapper.MAPPER_NAME, self.bus)
387
388 def valid_signal(self, name):
389 if self.discovery_pending():
390 return False
391
392 if obmc.dbuslib.bindings.is_unique(name):
393 name = self.bus_map.get(name)
394
395 return name is not None and \
396 self.bus_match(name)
397
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()