blob: f8286cebed49758cb8bc6c476e0ae19093b0f064 [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
Brad Bishop329472a2016-09-08 16:12:14 -040057 def _invoke_method(path, iface, method):
Brad Bishop63f59a72016-07-25 12:05:57 -040058 obj = _FindInterfaces._get_object(path)
59 if not obj:
60 return None
61
62 iface = dbus.Interface(obj, iface)
63 try:
Brad Bishop329472a2016-09-08 16:12:14 -040064 return method(iface)
Brad Bishop63f59a72016-07-25 12:05:57 -040065 except dbus.exceptions.DBusException, e:
66 if e.get_dbus_name() in [
67 obmc.dbuslib.enums.DBUS_UNKNOWN_SERVICE,
68 obmc.dbuslib.enums.DBUS_NO_REPLY]:
69 print "Warning: Introspection failure: " \
70 "service `%s` did not reply to "\
71 "method call on %s" % (service, path)
72 return None
73 raise
74
75 @staticmethod
76 def _introspect(path):
77 return _FindInterfaces._invoke_method(
78 path,
79 dbus.INTROSPECTABLE_IFACE,
Brad Bishop329472a2016-09-08 16:12:14 -040080 lambda x: x.Introspect())
Brad Bishop63f59a72016-07-25 12:05:57 -040081
82 @staticmethod
83 def _get_managed_objects(om):
84 return _FindInterfaces._invoke_method(
85 om,
86 dbus.BUS_DAEMON_IFACE + '.ObjectManager',
Brad Bishop329472a2016-09-08 16:12:14 -040087 lambda x: x.GetManagedObjects())
Brad Bishop63f59a72016-07-25 12:05:57 -040088
89 @staticmethod
90 def _to_path(elements):
91 return '/' + '/'.join(elements)
92
93 @staticmethod
94 def _to_path_elements(path):
95 return filter(bool, path.split('/'))
96
97 def __call__(self, path):
98 self.results = {}
99 self._find_interfaces(path)
100 return self.results
101
102 @staticmethod
103 def _match(iface):
104 return iface == dbus.BUS_DAEMON_IFACE + '.ObjectManager' \
105 or match(iface)
106
107 def _find_interfaces(self, path):
108 path_elements = self._to_path_elements(path)
109 path = self._to_path(path_elements)
110 data = self._introspect(path)
111 if data is None:
112 return
113
114 root = ET.fromstring(data)
115 ifaces = filter(
116 self._match,
117 [x.attrib.get('name') for x in root.findall('interface')])
118 self.results[path] = ifaces
119
120 if dbus.BUS_DAEMON_IFACE + '.ObjectManager' in ifaces:
121 objs = self._get_managed_objects(path)
122 for k, v in objs.iteritems():
123 self.results[k] = v
124 else:
125 children = filter(
126 bool,
127 [x.attrib.get('name') for x in root.findall('node')])
128 children = [
129 self._to_path(
130 path_elements + self._to_path_elements(x))
131 for x in sorted(children)]
132 for child in children:
133 if child not in self.results:
134 self._find_interfaces(child)
135
136 return _FindInterfaces()(path)
137
138
139class Association(dbus.service.Object):
140 def __init__(self, bus, path, endpoints):
Brad Bishop70dd5952016-09-08 22:33:33 -0400141 super(Association, self).__init__(conn=bus, object_path=path)
Brad Bishop63f59a72016-07-25 12:05:57 -0400142 self.endpoints = endpoints
143
144 def __getattr__(self, name):
145 if name == 'properties':
146 return {
147 obmc.dbuslib.enums.OBMC_ASSOC_IFACE: {
148 'endpoints': self.endpoints}}
149 return super(Association, self).__getattr__(name)
150
151 def emit_signal(self, old):
152 if old != self.endpoints:
153 self.PropertiesChanged(
154 obmc.dbuslib.enums.OBMC_ASSOC_IFACE,
155 {'endpoints': self.endpoints}, ['endpoints'])
156
157 def append(self, endpoints):
158 old = self.endpoints
159 self.endpoints = list(set(endpoints).union(self.endpoints))
160 self.emit_signal(old)
161
162 def remove(self, endpoints):
163 old = self.endpoints
164 self.endpoints = list(set(self.endpoints).difference(endpoints))
165 self.emit_signal(old)
166
167 @dbus.service.method(dbus.PROPERTIES_IFACE, 'ss', 'as')
168 def Get(self, interface_name, property_name):
169 if property_name != 'endpoints':
170 raise dbus.exceptions.DBusException(name=DBUS_UNKNOWN_PROPERTY)
171 return self.GetAll(interface_name)[property_name]
172
173 @dbus.service.method(dbus.PROPERTIES_IFACE, 's', 'a{sas}')
174 def GetAll(self, interface_name):
175 if interface_name != obmc.dbuslib.enums.OBMC_ASSOC_IFACE:
176 raise dbus.exceptions.DBusException(DBUS_UNKNOWN_INTERFACE)
177 return {'endpoints': self.endpoints}
178
179 @dbus.service.signal(
180 dbus.PROPERTIES_IFACE, signature='sa{sas}as')
181 def PropertiesChanged(
182 self, interface_name, changed_properties, invalidated_properties):
183 pass
184
185
186class Manager(obmc.dbuslib.bindings.DbusObjectManager):
187 def __init__(self, bus, path):
Brad Bishop70dd5952016-09-08 22:33:33 -0400188 super(Manager, self).__init__(conn=bus, object_path=path)
Brad Bishop63f59a72016-07-25 12:05:57 -0400189
190
191class ObjectMapper(dbus.service.Object):
192 def __init__(self, bus, path,
193 name_match=obmc.utils.misc.org_dot_openbmc_match,
194 intf_match=obmc.utils.misc.org_dot_openbmc_match):
195 super(ObjectMapper, self).__init__(bus, path)
196 self.cache = obmc.utils.pathtree.PathTree()
197 self.bus = bus
198 self.name_match = name_match
199 self.intf_match = intf_match
200 self.tag_match = obmc.utils.misc.ListMatch(['children', 'interface'])
201 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
231 def bus_match(self, owner):
232 # Ignore my own signals
233 return owner != obmc.mapper.MAPPER_NAME and \
234 self.name_match(owner)
235
236 def discovery_pending(self):
237 return not bool(self.service)
238
239 def cache_get(self, path):
240 cache_entry = self.cache.get(path, {})
241 if cache_entry is None:
242 # hide path elements without any interfaces
243 cache_entry = {}
244 return cache_entry
245
246 def add_new_objmgr(self, path, owner):
247 # We don't get a signal for the ObjectManager
248 # interface itself, so if we see a signal from
249 # make sure its in our cache, and add it if not.
250 cache_entry = self.cache_get(path)
251 old = self.interfaces_get(cache_entry, owner)
252 new = list(set(old).union([dbus.BUS_DAEMON_IFACE + '.ObjectManager']))
253 self.update_interfaces(path, owner, old, new)
254
255 def interfaces_added_handler(self, path, iprops, **kw):
256 path = str(path)
257 owner = str(kw['sender'])
258 interfaces = self.get_signal_interfaces(owner, iprops.iterkeys())
259 if interfaces:
260 self.add_new_objmgr(str(kw['sender_path']), owner)
261 cache_entry = self.cache_get(path)
262 old = self.interfaces_get(cache_entry, owner)
263 new = list(set(interfaces).union(old))
264 self.update_interfaces(path, owner, old, new)
265
266 def interfaces_removed_handler(self, path, interfaces, **kw):
267 path = str(path)
268 owner = str(kw['sender'])
269 interfaces = self.get_signal_interfaces(owner, interfaces)
270 if interfaces:
271 self.add_new_objmgr(str(kw['sender_path']), owner)
272 cache_entry = self.cache_get(path)
273 old = self.interfaces_get(cache_entry, owner)
274 new = list(set(old).difference(interfaces))
275 self.update_interfaces(path, owner, old, new)
276
277 def properties_changed_handler(self, interface, new, old, **kw):
278 owner = str(kw['sender'])
279 path = str(kw['path'])
280 interfaces = self.get_signal_interfaces(owner, [interface])
281 if not self.is_association(interfaces):
282 return
283 associations = new.get('associations', None)
284 if associations is None:
285 return
286
287 associations = [
288 (str(x), str(y), str(z)) for x, y, z in associations]
289 self.update_associations(
290 path, owner,
291 self.index_get_associations(path, [owner]),
292 associations)
293
294 def process_new_owner(self, owned_name, owner):
295 # unique name
296 try:
297 return self.discover([(owned_name, owner)])
298 except dbus.exceptions.DBusException, e:
299 if obmc.dbuslib.enums.DBUS_UNKNOWN_SERVICE \
300 not in e.get_dbus_name():
301 raise
302
303 def process_old_owner(self, owned_name, owner):
304 if owner in self.bus_map:
305 del self.bus_map[owner]
306
307 for path, item in self.cache.dataitems():
308 old = self.interfaces_get(item, owner)
309 # remove all interfaces for this service
310 self.update_interfaces(
311 path, owner, old=old, new=[])
312
313 def bus_handler(self, owned_name, old, new):
314 valid = False
315 if not obmc.dbuslib.bindings.is_unique(owned_name):
316 valid = self.valid_signal(owned_name)
317
318 if valid and new:
319 self.process_new_owner(owned_name, new)
320 if valid and old:
321 self.process_old_owner(owned_name, old)
322
323 def update_interfaces(self, path, owner, old, new):
324 # __xx -> intf list
325 # xx -> intf dict
326 if isinstance(old, dict):
327 __old = old.keys()
328 else:
329 __old = old
330 old = {x: {} for x in old}
331 if isinstance(new, dict):
332 __new = new.keys()
333 else:
334 __new = new
335 new = {x: {} for x in new}
336
337 cache_entry = self.cache.setdefault(path, {})
338 created = [] if self.has_interfaces(cache_entry) else [path]
339 added = list(set(__new).difference(__old))
340 removed = list(set(__old).difference(__new))
341 self.interfaces_append(cache_entry, owner, added)
342 self.interfaces_remove(cache_entry, owner, removed, path)
343 destroyed = [] if self.has_interfaces(cache_entry) else [path]
344
345 # react to anything that requires association updates
346 new_assoc = []
347 old_assoc = []
348 if self.is_association(added):
349 new_assoc = self.dbus_get_associations(path, owner, new)
350 if self.is_association(removed):
351 old_assoc = self.index_get_associations(path, [owner])
352 self.update_associations(
353 path, owner, old_assoc, new_assoc, created, destroyed)
354
355 def add_items(self, owner, bus_items):
356 for path, items in bus_items.iteritems():
357 self.update_interfaces(path, str(owner), old=[], new=items)
358
359 def discover(self, owners=[]):
360 def match(iface):
361 return iface == dbus.BUS_DAEMON_IFACE + '.ObjectManager' or \
362 self.intf_match(iface)
363 if not owners:
364 owned_names = [
365 x for x in self.bus.list_names() if self.bus_match(x)]
366 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,
371 find_dbus_interfaces(self.bus, o, '/', self.intf_match))
372 self.bus_map[o] = owned_name
373
374 if self.discovery_pending():
375 # add my object mananger instance
376 self.bus_map[self.unique] = obmc.mapper.MAPPER_NAME
377 self.add_items(
378 self.unique,
379 {obmc.dbuslib.bindings.OBJ_PREFIX:
Brad Bishop22ff24a2016-07-25 12:26:15 -0400380 [dbus.BUS_DAEMON_IFACE + '.ObjectManager']})
Brad Bishop63f59a72016-07-25 12:05:57 -0400381
382 print "ObjectMapper discovery complete..."
383 self.service = dbus.service.BusName(
384 obmc.mapper.MAPPER_NAME, self.bus)
385
386 def valid_signal(self, name):
387 if self.discovery_pending():
388 return False
389
390 if obmc.dbuslib.bindings.is_unique(name):
391 name = self.bus_map.get(name)
392
393 return name is not None and \
394 self.bus_match(name)
395
396 def get_signal_interfaces(self, owner, interfaces):
397 filtered = []
398 if self.valid_signal(owner):
399 filtered = [str(x) for x in interfaces if self.intf_match(x)]
400
401 return filtered
402
403 @staticmethod
404 def interfaces_get(item, owner, default=[]):
405 return item.get(owner, default)
406
407 @staticmethod
408 def interfaces_append(item, owner, append):
409 interfaces = item.setdefault(owner, [])
410 item[owner] = list(set(append).union(interfaces))
411
412 def interfaces_remove(self, item, owner, remove, path):
413 interfaces = item.get(owner, [])
414 item[owner] = list(set(interfaces).difference(remove))
415
416 if not item[owner]:
417 # remove the owner if there aren't any interfaces left
418 del item[owner]
419
420 if item:
421 # other owners remain
422 return
423
424 if self.cache.get_children(path):
425 # there are still references to this path
426 # from objects further down the tree.
427 # mark it for removal if that changes
428 self.cache.demote(path)
429 else:
430 # delete the entire path if everything is gone
431 del self.cache[path]
432
433 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 's', 'a{sas}')
434 def GetObject(self, path):
435 o = self.cache_get(path)
436 if not o:
437 raise MapperNotFoundException(path)
438 return o
439
440 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 'si', 'as')
441 def GetSubTreePaths(self, path, depth):
442 try:
443 return self.cache.iterkeys(path, depth)
444 except KeyError:
445 raise MapperNotFoundException(path)
446
447 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 'si', 'a{sa{sas}}')
448 def GetSubTree(self, path, depth):
449 try:
450 return {x: y for x, y in self.cache.dataitems(path, depth)}
451 except KeyError:
452 raise MapperNotFoundException(path)
453
454 @staticmethod
455 def has_interfaces(item):
456 for owner in item.iterkeys():
457 if ObjectMapper.interfaces_get(item, owner):
458 return True
459 return False
460
461 @staticmethod
462 def is_association(interfaces):
463 return obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE in interfaces
464
465 def index_get(self, index, path, owners):
466 items = []
467 item = self.index.get(index, {})
468 item = item.get(path, {})
469 for o in owners:
470 items.extend(item.get(o, []))
471 return items
472
473 def index_append(self, index, path, owner, assoc):
474 item = self.index.setdefault(index, {})
475 item = item.setdefault(path, {})
476 item = item.setdefault(owner, [])
477 item.append(assoc)
478
479 def index_remove(self, index, path, owner, assoc):
480 index = self.index.get(index, {})
481 owners = index.get(path, {})
482 items = owners.get(owner, [])
483 if assoc in items:
484 items.remove(assoc)
485 if not items:
486 del owners[owner]
487 if not owners:
488 del index[path]
489
490 def dbus_get_associations(self, path, owner, obj):
491 iface = obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE
492 if 'associations' in obj[iface]:
493 return obj[iface]['associations']
494
495 # fallback to dbus
496 obj = self.bus.get_object(owner, path, introspect=False)
497 iface = dbus.Interface(obj, dbus.PROPERTIES_IFACE)
498 return [(str(f), str(r), str(e)) for f, r, e in iface.Get(
499 obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE,
500 'associations')]
501
502 def index_get_associations(self, path, owners=[], direction='forward'):
503 forward = 'forward' if direction == 'forward' else 'reverse'
504 reverse = 'reverse' if direction == 'forward' else 'forward'
505
506 associations = []
507 if not owners:
508 index = self.index.get(forward, {})
509 owners = index.get(path, {}).keys()
510
511 # f: forward
512 # r: reverse
513 for rassoc in self.index_get(forward, path, owners):
514 elements = rassoc.split('/')
515 rtype = ''.join(elements[-1:])
516 fendpoint = '/'.join(elements[:-1])
517 for fassoc in self.index_get(reverse, fendpoint, owners):
518 elements = fassoc.split('/')
519 ftype = ''.join(elements[-1:])
520 rendpoint = '/'.join(elements[:-1])
521 if rendpoint != path:
522 continue
523 associations.append((ftype, rtype, fendpoint))
524
525 return associations
526
527 def update_association(self, path, removed, added):
528 iface = obmc.dbuslib.enums.OBMC_ASSOC_IFACE
529 create = [] if self.manager.get(path, False) else [iface]
530
531 if added and create:
532 self.manager.add(
533 path, Association(self.bus, path, added))
534 elif added:
535 self.manager.get(path).append(added)
536
537 obj = self.manager.get(path, None)
538 if obj and removed:
539 obj.remove(removed)
540
541 if obj and not obj.endpoints:
542 self.manager.remove(path)
543
544 delete = [] if self.manager.get(path, False) else [iface]
545
546 if create != delete:
547 self.update_interfaces(
548 path, self.unique, delete, create)
549
550 def update_associations(
551 self, path, owner, old, new, created=[], destroyed=[]):
552 added = list(set(new).difference(old))
553 removed = list(set(old).difference(new))
554 for forward, reverse, endpoint in added:
555 # update the index
556 forward_path = str(path + '/' + forward)
557 reverse_path = str(endpoint + '/' + reverse)
558 self.index_append(
559 'forward', path, owner, reverse_path)
560 self.index_append(
561 'reverse', endpoint, owner, forward_path)
562
563 # create the association if the endpoint exists
564 if not self.cache_get(endpoint):
565 continue
566
567 self.update_association(forward_path, [], [endpoint])
568 self.update_association(reverse_path, [], [path])
569
570 for forward, reverse, endpoint in removed:
571 # update the index
572 forward_path = str(path + '/' + forward)
573 reverse_path = str(endpoint + '/' + reverse)
574 self.index_remove(
575 'forward', path, owner, reverse_path)
576 self.index_remove(
577 'reverse', endpoint, owner, forward_path)
578
579 # destroy the association if it exists
580 self.update_association(forward_path, [endpoint], [])
581 self.update_association(reverse_path, [path], [])
582
583 # If the associations interface endpoint comes
584 # or goes create or destroy the appropriate
585 # associations
586 for path in created:
587 for forward, reverse, endpoint in \
588 self.index_get_associations(path, direction='reverse'):
589 forward_path = str(path + '/' + forward)
590 reverse_path = str(endpoint + '/' + reverse)
591 self.update_association(forward_path, [], [endpoint])
592 self.update_association(reverse_path, [], [path])
593
594 for path in destroyed:
595 for forward, reverse, endpoint in \
596 self.index_get_associations(path, direction='reverse'):
597 forward_path = str(path + '/' + forward)
598 reverse_path = str(endpoint + '/' + reverse)
599 self.update_association(forward_path, [endpoint], [])
600 self.update_association(reverse_path, [path], [])
601
602 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 's', 'a{sa{sas}}')
603 def GetAncestors(self, path):
604 elements = filter(bool, path.split('/'))
605 paths = []
606 objs = {}
607 while elements:
608 elements.pop()
609 paths.append('/' + '/'.join(elements))
610 if path != '/':
611 paths.append('/')
612
613 for path in paths:
614 obj = self.cache_get(path)
615 if not obj:
616 continue
617 objs[path] = obj
618
619 return objs
620
621
622def server_main():
623 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
624 bus = dbus.SystemBus()
625 o = ObjectMapper(bus, obmc.mapper.MAPPER_PATH)
626 loop = gobject.MainLoop()
627
628 loop.run()