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