blob: 8d23a4c86110a3658dac91fbbed4611ad28d9467 [file] [log] [blame]
Brad Bishopeded8f32017-11-01 11:22:38 -04001# Contributors Listed Below - COPYRIGHT 2017
Brad Bishop63f59a72016-07-25 12:05:57 -04002# [+] 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
CamVan Nguyen2fd4b1f2018-03-05 12:19:46 -060021# TODO: openbmc/openbmc#2994 remove python 2 support
22try: # python 2
23 import gobject
24except ImportError: # python 3
25 from gi.repository import GObject as gobject
Brad Bishop63f59a72016-07-25 12:05:57 -040026import xml.etree.ElementTree as ET
27import obmc.utils.pathtree
Brad Bishop63f59a72016-07-25 12:05:57 -040028import obmc.mapper
29import obmc.dbuslib.bindings
30import obmc.dbuslib.enums
Brad Bishop99b8bc82017-07-29 21:39:52 -040031import sys
32import traceback
Andrew Jefferyb86b63a2018-05-09 16:10:46 +093033from itertools import chain
Brad Bishop63f59a72016-07-25 12:05:57 -040034
35
Brad Bishop2e0436c2016-09-19 18:02:19 -040036class MapperBusyException(dbus.exceptions.DBusException):
37 _dbus_error_name = 'org.freedesktop.DBus.Error.ObjectPathInUse'
38
39 def __init__(self):
40 super(MapperBusyException, self).__init__(
41 'busy processing bus traffic')
42
43
Brad Bishop63f59a72016-07-25 12:05:57 -040044class MapperNotFoundException(dbus.exceptions.DBusException):
45 _dbus_error_name = obmc.mapper.MAPPER_NOT_FOUND
46
47 def __init__(self, path):
48 super(MapperNotFoundException, self).__init__(
49 "path or object not found: %s" % path)
50
51
Brad Bishop520473f2016-09-19 21:46:36 -040052def find_dbus_interfaces(conn, service, path, callback, error_callback, **kw):
Brad Bishopbd8aa052016-09-19 09:30:06 -040053 iface_match = kw.pop('iface_match', bool)
Brad Bishop6a0320b2016-09-19 11:03:06 -040054 subtree_match = kw.pop('subtree_match', bool)
Brad Bishopbd8aa052016-09-19 09:30:06 -040055
Brad Bishop63f59a72016-07-25 12:05:57 -040056 class _FindInterfaces(object):
57 def __init__(self):
58 self.results = {}
Brad Bishop520473f2016-09-19 21:46:36 -040059 self.introspect_pending = []
60 self.gmo_pending = []
61 self.assoc_pending = []
Brad Bishop63f59a72016-07-25 12:05:57 -040062
63 @staticmethod
64 def _to_path(elements):
65 return '/' + '/'.join(elements)
66
67 @staticmethod
68 def _to_path_elements(path):
Andrew Jefferyb86b63a2018-05-09 16:10:46 +093069 return filter(bool, path.split('/'))
Brad Bishop63f59a72016-07-25 12:05:57 -040070
71 def __call__(self, path):
Brad Bishop520473f2016-09-19 21:46:36 -040072 try:
73 self._find_interfaces(path)
Balaji B Rao84e331a2017-11-09 21:19:13 -060074 except Exception as e:
Brad Bishop520473f2016-09-19 21:46:36 -040075 error_callback(service, path, e)
Brad Bishop63f59a72016-07-25 12:05:57 -040076
77 @staticmethod
78 def _match(iface):
79 return iface == dbus.BUS_DAEMON_IFACE + '.ObjectManager' \
Brad Bishopbd8aa052016-09-19 09:30:06 -040080 or iface_match(iface)
Brad Bishop63f59a72016-07-25 12:05:57 -040081
Brad Bishop520473f2016-09-19 21:46:36 -040082 def check_done(self):
83 if any([
84 self.introspect_pending,
85 self.gmo_pending,
86 self.assoc_pending]):
87 return
88
89 callback(service, self.results)
90
91 def _assoc_callback(self, path, associations):
92 try:
93 iface = obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE
94 self.assoc_pending.remove(path)
Gunnar Mills296395c2017-09-06 13:56:43 -050095 self.results[path][iface]['associations'] = associations
Balaji B Rao84e331a2017-11-09 21:19:13 -060096 except Exception as e:
Brad Bishop520473f2016-09-19 21:46:36 -040097 error_callback(service, path, e)
98 return None
99
100 self.check_done()
101
102 def _gmo_callback(self, path, objs):
103 try:
104 self.gmo_pending.remove(path)
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930105 for k, v in objs.items():
106 self.results[k] = dict(x for x in v.items()
107 if iface_match(x[0]))
Balaji B Rao84e331a2017-11-09 21:19:13 -0600108 except Exception as e:
Brad Bishop520473f2016-09-19 21:46:36 -0400109 error_callback(service, path, e)
110 return None
111
112 self.check_done()
113
114 def _introspect_callback(self, path, data):
115 self.introspect_pending.remove(path)
116 if data is None:
117 self.check_done()
118 return
119
120 try:
121 path_elements = self._to_path_elements(path)
122 root = ET.fromstring(data)
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930123 all_ifaces = root.findall('interface')
124 ifaces = filter(self._match,
125 [x.attrib.get('name') for x in all_ifaces])
Brad Bishop520473f2016-09-19 21:46:36 -0400126 ifaces = {x: {} for x in ifaces}
127 self.results[path] = ifaces
128
129 if obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE in ifaces:
130 obj = conn.get_object(service, path, introspect=False)
131 iface = dbus.Interface(obj, dbus.PROPERTIES_IFACE)
132 self.assoc_pending.append(path)
133 iface.Get.call_async(
134 obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE,
135 'associations',
136 reply_handler=lambda x: self._assoc_callback(
137 path, x),
138 error_handler=lambda e: error_callback(
139 service, path, e))
140
141 if dbus.BUS_DAEMON_IFACE + '.ObjectManager' in ifaces:
142 obj = conn.get_object(service, path, introspect=False)
143 iface = dbus.Interface(
144 obj, dbus.BUS_DAEMON_IFACE + '.ObjectManager')
145 self.gmo_pending.append(path)
146 iface.GetManagedObjects.call_async(
147 reply_handler=lambda x: self._gmo_callback(
148 path, x),
149 error_handler=lambda e: error_callback(
150 service, path, e))
151 else:
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930152 children = filter(
Brad Bishop520473f2016-09-19 21:46:36 -0400153 bool,
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930154 [x.attrib.get('name') for x in root.findall('node')])
Brad Bishop520473f2016-09-19 21:46:36 -0400155 children = [
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930156 self._to_path(chain(path_elements,
157 self._to_path_elements(x)))
Brad Bishop520473f2016-09-19 21:46:36 -0400158 for x in sorted(children)]
159 for child in filter(subtree_match, children):
160 if child not in self.results:
161 self._find_interfaces(child)
Balaji B Rao84e331a2017-11-09 21:19:13 -0600162 except Exception as e:
Brad Bishop520473f2016-09-19 21:46:36 -0400163 error_callback(service, path, e)
164 return None
165
166 self.check_done()
167
Brad Bishop63f59a72016-07-25 12:05:57 -0400168 def _find_interfaces(self, path):
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930169 path = self._to_path(self._to_path_elements(path))
Brad Bishop520473f2016-09-19 21:46:36 -0400170 obj = conn.get_object(service, path, introspect=False)
171 iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE)
172 self.introspect_pending.append(path)
173 iface.Introspect.call_async(
174 reply_handler=lambda x: self._introspect_callback(path, x),
175 error_handler=lambda x: error_callback(service, path, x))
Brad Bishop63f59a72016-07-25 12:05:57 -0400176
177 return _FindInterfaces()(path)
178
179
Brad Bishopc33ae652017-11-02 22:23:09 -0400180@obmc.dbuslib.bindings.add_interfaces([obmc.dbuslib.enums.OBMC_ASSOC_IFACE])
181class Association(obmc.dbuslib.bindings.DbusProperties):
Brad Bishop734b2c32017-11-01 15:40:07 -0400182 """Implementation of org.openbmc.Association."""
183
Brad Bishopb9b3ed52017-11-01 21:40:31 -0400184 iface = obmc.dbuslib.enums.OBMC_ASSOC_IFACE
185
Brad Bishop63f59a72016-07-25 12:05:57 -0400186 def __init__(self, bus, path, endpoints):
Brad Bishop734b2c32017-11-01 15:40:07 -0400187 """Construct an Association.
188
189 Arguments:
190 bus -- The python-dbus connection to host the interface
191 path -- The D-Bus object path on which to implement the interface
192 endpoints -- A list of the initial association endpoints
193 """
Brad Bishop70dd5952016-09-08 22:33:33 -0400194 super(Association, self).__init__(conn=bus, object_path=path)
Brad Bishopb9b3ed52017-11-01 21:40:31 -0400195 self.properties = {self.iface: {'endpoints': endpoints}}
Brad Bishopbcc06442018-01-29 14:54:51 -0500196 self.unmask_signals()
Brad Bishop63f59a72016-07-25 12:05:57 -0400197
Brad Bishop63f59a72016-07-25 12:05:57 -0400198
199class Manager(obmc.dbuslib.bindings.DbusObjectManager):
200 def __init__(self, bus, path):
Brad Bishop70dd5952016-09-08 22:33:33 -0400201 super(Manager, self).__init__(conn=bus, object_path=path)
Brad Bishop63f59a72016-07-25 12:05:57 -0400202
203
204class ObjectMapper(dbus.service.Object):
Brad Bishopcb2e1b32017-07-09 20:11:35 -0400205 def __init__(
206 self, bus, path, namespaces, interface_namespaces,
207 blacklist, interface_blacklist):
Brad Bishop63f59a72016-07-25 12:05:57 -0400208 super(ObjectMapper, self).__init__(bus, path)
209 self.cache = obmc.utils.pathtree.PathTree()
210 self.bus = bus
Brad Bishop63f59a72016-07-25 12:05:57 -0400211 self.service = None
212 self.index = {}
213 self.manager = Manager(bus, obmc.dbuslib.bindings.OBJ_PREFIX)
Brad Bishop63f59a72016-07-25 12:05:57 -0400214 self.bus_map = {}
Brad Bishop2e0436c2016-09-19 18:02:19 -0400215 self.defer_signals = {}
Brad Bishopcb2e1b32017-07-09 20:11:35 -0400216 self.namespaces = namespaces
217 self.interface_namespaces = interface_namespaces
218 self.blacklist = blacklist
219 self.blacklist.append(obmc.mapper.MAPPER_PATH)
220 self.interface_blacklist = interface_blacklist
Brad Bishop63f59a72016-07-25 12:05:57 -0400221
Brad Bishop5d4890c2016-09-19 11:28:47 -0400222 # add my object mananger instance
Brad Bishop57255f62018-01-29 15:26:06 -0500223 self.add_new_objmgr(
224 obmc.dbuslib.bindings.OBJ_PREFIX, obmc.mapper.MAPPER_NAME)
Brad Bishop5d4890c2016-09-19 11:28:47 -0400225
Brad Bishop63f59a72016-07-25 12:05:57 -0400226 self.bus.add_signal_receiver(
227 self.bus_handler,
228 dbus_interface=dbus.BUS_DAEMON_IFACE,
229 signal_name='NameOwnerChanged')
230 self.bus.add_signal_receiver(
231 self.interfaces_added_handler,
232 dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager',
233 signal_name='InterfacesAdded',
234 sender_keyword='sender',
235 path_keyword='sender_path')
236 self.bus.add_signal_receiver(
237 self.interfaces_removed_handler,
238 dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager',
239 signal_name='InterfacesRemoved',
240 sender_keyword='sender',
241 path_keyword='sender_path')
242 self.bus.add_signal_receiver(
243 self.properties_changed_handler,
244 dbus_interface=dbus.PROPERTIES_IFACE,
245 signal_name='PropertiesChanged',
Brad Bishopb270adc2017-11-14 23:32:59 -0500246 arg0=obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE,
Brad Bishop63f59a72016-07-25 12:05:57 -0400247 path_keyword='path',
248 sender_keyword='sender')
249
Balaji B Rao84e331a2017-11-09 21:19:13 -0600250 print("ObjectMapper startup complete. Discovery in progress...")
Brad Bishop5d4890c2016-09-19 11:28:47 -0400251 self.discover()
Brad Bishop520473f2016-09-19 21:46:36 -0400252 gobject.idle_add(self.claim_name)
Brad Bishop5d4890c2016-09-19 11:28:47 -0400253
Brad Bishop520473f2016-09-19 21:46:36 -0400254 def claim_name(self):
255 if len(self.defer_signals):
256 return True
Balaji B Rao84e331a2017-11-09 21:19:13 -0600257 print("ObjectMapper discovery complete")
Brad Bishop5d4890c2016-09-19 11:28:47 -0400258 self.service = dbus.service.BusName(
259 obmc.mapper.MAPPER_NAME, self.bus)
Brad Bishop55b89cd2016-09-19 23:02:48 -0400260 self.manager.unmask_signals()
Brad Bishop520473f2016-09-19 21:46:36 -0400261 return False
Brad Bishop63f59a72016-07-25 12:05:57 -0400262
Brad Bishop2e0436c2016-09-19 18:02:19 -0400263 def discovery_callback(self, owner, items):
264 if owner in self.defer_signals:
265 self.add_items(owner, items)
266 pending = self.defer_signals[owner]
267 del self.defer_signals[owner]
268
269 for x in pending:
270 x()
Brad Bishop829181d2017-02-24 09:49:14 -0500271 self.IntrospectionComplete(owner)
Brad Bishop2e0436c2016-09-19 18:02:19 -0400272
273 def discovery_error(self, owner, path, e):
Brad Bishop99b8bc82017-07-29 21:39:52 -0400274 '''Log a message and remove all traces of the service
275 we were attempting to introspect.'''
276
Brad Bishop2e0436c2016-09-19 18:02:19 -0400277 if owner in self.defer_signals:
Brad Bishop7a790272017-12-14 21:25:24 -0500278
279 # Safe to add a reference to the traceback here,
280 # since it cannot contain the discovery_error frame.
281 exctype, value, tb = sys.exc_info()
Brad Bishop99b8bc82017-07-29 21:39:52 -0400282 sys.stderr.write(
Brad Bishop57255f62018-01-29 15:26:06 -0500283 '{} discovery failure on {}\n'.format(owner, path))
Brad Bishop7a790272017-12-14 21:25:24 -0500284 if tb:
285 traceback.print_exception(exctype, value, tb, file=sys.stderr)
286 else:
287 sys.stderr.write('{}: {}\n'.format(e.__class__.__name__, e))
288
Brad Bishop99b8bc82017-07-29 21:39:52 -0400289 del self.defer_signals[owner]
290 del self.bus_map[owner]
Brad Bishop2e0436c2016-09-19 18:02:19 -0400291
Brad Bishop63f59a72016-07-25 12:05:57 -0400292 def cache_get(self, path):
293 cache_entry = self.cache.get(path, {})
294 if cache_entry is None:
295 # hide path elements without any interfaces
296 cache_entry = {}
297 return cache_entry
298
299 def add_new_objmgr(self, path, owner):
300 # We don't get a signal for the ObjectManager
301 # interface itself, so if we see a signal from
302 # make sure its in our cache, and add it if not.
303 cache_entry = self.cache_get(path)
304 old = self.interfaces_get(cache_entry, owner)
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930305 new = set(old).union([dbus.BUS_DAEMON_IFACE + '.ObjectManager'])
Brad Bishop63f59a72016-07-25 12:05:57 -0400306 self.update_interfaces(path, owner, old, new)
307
Brad Bishop2e0436c2016-09-19 18:02:19 -0400308 def defer_signal(self, owner, callback):
309 self.defer_signals.setdefault(owner, []).append(callback)
310
Brad Bishop63f59a72016-07-25 12:05:57 -0400311 def interfaces_added_handler(self, path, iprops, **kw):
312 path = str(path)
Brad Bishop57255f62018-01-29 15:26:06 -0500313 owner = self.bus_normalize(str(kw['sender']))
314 if not owner:
Brad Bishop787aa812018-01-28 23:42:03 -0500315 return
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930316 interfaces = self.filter_signal_interfaces(iprops.keys())
Brad Bishop2e0436c2016-09-19 18:02:19 -0400317 if not interfaces:
318 return
319
320 if owner not in self.defer_signals:
Brad Bishop63f59a72016-07-25 12:05:57 -0400321 self.add_new_objmgr(str(kw['sender_path']), owner)
322 cache_entry = self.cache_get(path)
323 old = self.interfaces_get(cache_entry, owner)
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930324 new = set(interfaces).union(old)
Brad Bishopa6235962017-06-07 23:56:54 -0400325 new = {x: iprops.get(x, {}) for x in new}
Brad Bishop63f59a72016-07-25 12:05:57 -0400326 self.update_interfaces(path, owner, old, new)
Brad Bishop2e0436c2016-09-19 18:02:19 -0400327 else:
328 self.defer_signal(
329 owner,
330 lambda: self.interfaces_added_handler(
331 path, iprops, **kw))
Brad Bishop63f59a72016-07-25 12:05:57 -0400332
333 def interfaces_removed_handler(self, path, interfaces, **kw):
334 path = str(path)
Brad Bishop57255f62018-01-29 15:26:06 -0500335 owner = self.bus_normalize(str(kw['sender']))
336 if not owner:
Brad Bishop787aa812018-01-28 23:42:03 -0500337 return
338 interfaces = self.filter_signal_interfaces(interfaces)
Brad Bishop2e0436c2016-09-19 18:02:19 -0400339 if not interfaces:
340 return
341
342 if owner not in self.defer_signals:
Brad Bishop63f59a72016-07-25 12:05:57 -0400343 self.add_new_objmgr(str(kw['sender_path']), owner)
344 cache_entry = self.cache_get(path)
345 old = self.interfaces_get(cache_entry, owner)
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930346 new = set(old).difference(interfaces)
Brad Bishop63f59a72016-07-25 12:05:57 -0400347 self.update_interfaces(path, owner, old, new)
Brad Bishop2e0436c2016-09-19 18:02:19 -0400348 else:
349 self.defer_signal(
350 owner,
351 lambda: self.interfaces_removed_handler(
352 path, interfaces, **kw))
Brad Bishop63f59a72016-07-25 12:05:57 -0400353
354 def properties_changed_handler(self, interface, new, old, **kw):
Brad Bishop57255f62018-01-29 15:26:06 -0500355 owner = self.bus_normalize(str(kw['sender']))
Brad Bishop63f59a72016-07-25 12:05:57 -0400356 path = str(kw['path'])
Brad Bishop57255f62018-01-29 15:26:06 -0500357 if not owner:
Brad Bishop787aa812018-01-28 23:42:03 -0500358 return
359 interfaces = self.filter_signal_interfaces([interface])
Brad Bishop63f59a72016-07-25 12:05:57 -0400360 if not self.is_association(interfaces):
361 return
362 associations = new.get('associations', None)
363 if associations is None:
364 return
365
Brad Bishop2e0436c2016-09-19 18:02:19 -0400366 if owner not in self.defer_signals:
367 associations = [
368 (str(x), str(y), str(z)) for x, y, z in associations]
369 self.update_associations(
370 path, owner,
371 self.index_get_associations(path, [owner]),
372 associations)
373 else:
374 self.defer_signal(
375 owner,
376 lambda: self.properties_changed_handler(
377 interface, new, old, **kw))
Brad Bishop63f59a72016-07-25 12:05:57 -0400378
379 def process_new_owner(self, owned_name, owner):
380 # unique name
381 try:
382 return self.discover([(owned_name, owner)])
Balaji B Rao84e331a2017-11-09 21:19:13 -0600383 except dbus.exceptions.DBusException as e:
Brad Bishop63f59a72016-07-25 12:05:57 -0400384 if obmc.dbuslib.enums.DBUS_UNKNOWN_SERVICE \
385 not in e.get_dbus_name():
386 raise
387
388 def process_old_owner(self, owned_name, owner):
389 if owner in self.bus_map:
390 del self.bus_map[owner]
391
392 for path, item in self.cache.dataitems():
Brad Bishop57255f62018-01-29 15:26:06 -0500393 old = self.interfaces_get(item, owned_name)
Brad Bishop63f59a72016-07-25 12:05:57 -0400394 # remove all interfaces for this service
395 self.update_interfaces(
Brad Bishop57255f62018-01-29 15:26:06 -0500396 path, owned_name, old=old, new=[])
Brad Bishop63f59a72016-07-25 12:05:57 -0400397
398 def bus_handler(self, owned_name, old, new):
Brad Bishop45271cb2018-01-28 23:56:05 -0500399 if obmc.dbuslib.bindings.is_unique(owned_name) or \
400 owned_name == obmc.mapper.MAPPER_NAME:
401 return
Brad Bishop63f59a72016-07-25 12:05:57 -0400402
Brad Bishop45271cb2018-01-28 23:56:05 -0500403 if new:
Brad Bishop63f59a72016-07-25 12:05:57 -0400404 self.process_new_owner(owned_name, new)
Brad Bishop45271cb2018-01-28 23:56:05 -0500405 if old:
Brad Bishop2e0436c2016-09-19 18:02:19 -0400406 # discard any unhandled signals
407 # or in progress discovery
Brad Bishop57255f62018-01-29 15:26:06 -0500408 if owned_name in self.defer_signals:
409 del self.defer_signals[owned_name]
Brad Bishop2e0436c2016-09-19 18:02:19 -0400410
Brad Bishop63f59a72016-07-25 12:05:57 -0400411 self.process_old_owner(owned_name, old)
412
413 def update_interfaces(self, path, owner, old, new):
414 # __xx -> intf list
415 # xx -> intf dict
416 if isinstance(old, dict):
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930417 __old = old.keys()
Brad Bishop63f59a72016-07-25 12:05:57 -0400418 else:
419 __old = old
420 old = {x: {} for x in old}
421 if isinstance(new, dict):
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930422 __new = new.keys()
Brad Bishop63f59a72016-07-25 12:05:57 -0400423 else:
424 __new = new
425 new = {x: {} for x in new}
426
427 cache_entry = self.cache.setdefault(path, {})
428 created = [] if self.has_interfaces(cache_entry) else [path]
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930429 added = set(__new).difference(__old)
430 removed = set(__old).difference(__new)
Brad Bishop63f59a72016-07-25 12:05:57 -0400431 self.interfaces_append(cache_entry, owner, added)
432 self.interfaces_remove(cache_entry, owner, removed, path)
433 destroyed = [] if self.has_interfaces(cache_entry) else [path]
434
435 # react to anything that requires association updates
436 new_assoc = []
437 old_assoc = []
438 if self.is_association(added):
Brad Bishop926b35d2016-09-19 14:20:04 -0400439 iface = obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE
440 new_assoc = new[iface]['associations']
Brad Bishop63f59a72016-07-25 12:05:57 -0400441 if self.is_association(removed):
442 old_assoc = self.index_get_associations(path, [owner])
443 self.update_associations(
444 path, owner, old_assoc, new_assoc, created, destroyed)
445
446 def add_items(self, owner, bus_items):
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930447 for path, items in bus_items.items():
Brad Bishop63f59a72016-07-25 12:05:57 -0400448 self.update_interfaces(path, str(owner), old=[], new=items)
449
Brad Bishopcb2e1b32017-07-09 20:11:35 -0400450 def path_match(self, path):
451 match = False
452
453 if not any([x for x in self.blacklist if x in path]):
454 # not blacklisted
455
456 if any([x for x in self.namespaces if x in path]):
457 # a watched namespace contains the path
458 match = True
459 elif any([path for x in self.namespaces if path in x]):
460 # the path contains a watched namespace
461 match = True
462
463 return match
464
465 def interface_match(self, interface):
466 match = True
467
468 if any([x for x in self.interface_blacklist if x in interface]):
469 # not blacklisted
470 match = False
471 elif not any([x for x in self.interface_namespaces if x in interface]):
472 # the interface contains a watched interface namespace
473 match = False
474
475 return match
476
Andrew Geissler140f4102018-02-19 08:31:05 -0800477 def discovery_error_retry(self, owner, path, e):
478 sys.stderr.write(
479 '{} discovery failure on {} - retry\n'.format(owner, path))
480 find_dbus_interfaces(self.bus, owner, '/',
481 self.discovery_callback,
482 self.discovery_error,
483 subtree_match=self.path_match,
484 iface_match=self.interface_match)
485
Andrew Jeffery60e0bb02018-05-15 09:21:10 +0930486 def discover(self, owners=None):
487 if owners is None:
488 owners = []
489
Brad Bishop062403d2017-07-29 22:43:40 -0400490 def get_owner(name):
491 try:
492 return (name, self.bus.get_name_owner(name))
Adriana Kobylaka1f24222018-01-10 16:09:07 -0600493 except Exception:
Brad Bishop062403d2017-07-29 22:43:40 -0400494 traceback.print_exception(*sys.exc_info())
495
Brad Bishop63f59a72016-07-25 12:05:57 -0400496 if not owners:
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930497 owned_names = [x for x in self.bus.list_names()
498 if not obmc.dbuslib.bindings.is_unique(x)]
499 owners = filter(bool, (get_owner(name) for name in owned_names))
Brad Bishop63f59a72016-07-25 12:05:57 -0400500 for owned_name, o in owners:
Brad Bishop5c5e13e2018-01-28 23:34:54 -0500501 if not self.bus_normalize(owned_name):
Brad Bishopaeac98b2017-07-29 22:56:48 -0400502 continue
Andrew Geissler12469242018-01-02 09:41:37 -0600503 self.bus_map[o] = owned_name
Brad Bishop57255f62018-01-29 15:26:06 -0500504 self.defer_signals[owned_name] = []
Brad Bishop520473f2016-09-19 21:46:36 -0400505 find_dbus_interfaces(
Brad Bishop57255f62018-01-29 15:26:06 -0500506 self.bus, owned_name, '/',
Brad Bishop520473f2016-09-19 21:46:36 -0400507 self.discovery_callback,
Andrew Geissler140f4102018-02-19 08:31:05 -0800508 self.discovery_error_retry,
Brad Bishopcb2e1b32017-07-09 20:11:35 -0400509 subtree_match=self.path_match,
510 iface_match=self.interface_match)
Brad Bishop63f59a72016-07-25 12:05:57 -0400511
Brad Bishop5c5e13e2018-01-28 23:34:54 -0500512 def bus_normalize(self, name):
513 '''
514 Normalize on well-known names and filter signals
515 originating from the mapper.
516 '''
517
Brad Bishop63f59a72016-07-25 12:05:57 -0400518 if obmc.dbuslib.bindings.is_unique(name):
519 name = self.bus_map.get(name)
520
Brad Bishop5c5e13e2018-01-28 23:34:54 -0500521 if name == obmc.mapper.MAPPER_NAME:
522 return None
523
524 return name
Brad Bishop63f59a72016-07-25 12:05:57 -0400525
Brad Bishop787aa812018-01-28 23:42:03 -0500526 def filter_signal_interfaces(self, interfaces):
527 return [str(x) for x in interfaces if self.interface_match(x)]
Brad Bishop63f59a72016-07-25 12:05:57 -0400528
529 @staticmethod
Andrew Jeffery60e0bb02018-05-15 09:21:10 +0930530 def interfaces_get(item, owner, default=None):
531 if default is None:
532 default = []
Brad Bishop63f59a72016-07-25 12:05:57 -0400533 return item.get(owner, default)
534
535 @staticmethod
536 def interfaces_append(item, owner, append):
537 interfaces = item.setdefault(owner, [])
538 item[owner] = list(set(append).union(interfaces))
539
540 def interfaces_remove(self, item, owner, remove, path):
541 interfaces = item.get(owner, [])
542 item[owner] = list(set(interfaces).difference(remove))
543
544 if not item[owner]:
545 # remove the owner if there aren't any interfaces left
546 del item[owner]
547
548 if item:
549 # other owners remain
550 return
551
552 if self.cache.get_children(path):
553 # there are still references to this path
554 # from objects further down the tree.
555 # mark it for removal if that changes
556 self.cache.demote(path)
557 else:
558 # delete the entire path if everything is gone
559 del self.cache[path]
560
Brad Bishop1c33c222016-11-02 00:08:46 -0400561 @staticmethod
562 def filter_interfaces(item, ifaces):
Andrew Jeffery4d49f952018-05-09 17:29:53 +0930563 return ObjectMapper._filter_interfaces(item, set(ifaces))
564
565 @staticmethod
566 def _filter_interfaces(item, ifaces):
Brad Bishop1c33c222016-11-02 00:08:46 -0400567 if isinstance(item, dict):
568 # Called with a single object.
569 if not ifaces:
570 return item
571
Andrew Jeffery4d49f952018-05-09 17:29:53 +0930572 filtered = dict()
573 for k, v in item.items():
574 isec = ifaces.intersection(v)
575 if isec:
576 filtered[k] = isec
Brad Bishop1c33c222016-11-02 00:08:46 -0400577
Andrew Jeffery4d49f952018-05-09 17:29:53 +0930578 return filtered
Brad Bishop1c33c222016-11-02 00:08:46 -0400579
580 # Called with a list of path/object tuples.
581 if not ifaces:
582 return dict(item)
583
Andrew Jeffery4d49f952018-05-09 17:29:53 +0930584 if not item:
585 return dict()
Brad Bishop1c33c222016-11-02 00:08:46 -0400586
Andrew Jeffery4d49f952018-05-09 17:29:53 +0930587 filtered = dict()
588 for i in item:
589 children = ObjectMapper._filter_interfaces(i[1], ifaces)
590 if children:
591 filtered[i[0]] = children
592
593 return filtered
Brad Bishop1c33c222016-11-02 00:08:46 -0400594
595 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 'sas', 'a{sas}')
596 def GetObject(self, path, interfaces):
Brad Bishop63f59a72016-07-25 12:05:57 -0400597 o = self.cache_get(path)
598 if not o:
599 raise MapperNotFoundException(path)
Brad Bishop63f59a72016-07-25 12:05:57 -0400600
Brad Bishop1c33c222016-11-02 00:08:46 -0400601 return self.filter_interfaces(o, interfaces)
602
603 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 'sias', 'as')
604 def GetSubTreePaths(self, path, depth, interfaces):
Brad Bishop63f59a72016-07-25 12:05:57 -0400605 try:
Brad Bishop24301972017-06-23 13:40:07 -0400606 return self.filter_interfaces(
607 self.cache.iteritems(path, depth),
608 interfaces)
Brad Bishop63f59a72016-07-25 12:05:57 -0400609 except KeyError:
610 raise MapperNotFoundException(path)
611
Brad Bishop1c33c222016-11-02 00:08:46 -0400612 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 'sias', 'a{sa{sas}}')
613 def GetSubTree(self, path, depth, interfaces):
Brad Bishop63f59a72016-07-25 12:05:57 -0400614 try:
Brad Bishop1c33c222016-11-02 00:08:46 -0400615 return self.filter_interfaces(
616 self.cache.dataitems(path, depth),
617 interfaces)
Brad Bishop63f59a72016-07-25 12:05:57 -0400618 except KeyError:
619 raise MapperNotFoundException(path)
620
621 @staticmethod
622 def has_interfaces(item):
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930623 return any(item.values())
Brad Bishop63f59a72016-07-25 12:05:57 -0400624
625 @staticmethod
626 def is_association(interfaces):
627 return obmc.dbuslib.enums.OBMC_ASSOCIATIONS_IFACE in interfaces
628
629 def index_get(self, index, path, owners):
630 items = []
631 item = self.index.get(index, {})
632 item = item.get(path, {})
633 for o in owners:
634 items.extend(item.get(o, []))
635 return items
636
637 def index_append(self, index, path, owner, assoc):
638 item = self.index.setdefault(index, {})
639 item = item.setdefault(path, {})
640 item = item.setdefault(owner, [])
641 item.append(assoc)
642
643 def index_remove(self, index, path, owner, assoc):
644 index = self.index.get(index, {})
645 owners = index.get(path, {})
646 items = owners.get(owner, [])
647 if assoc in items:
648 items.remove(assoc)
649 if not items:
650 del owners[owner]
651 if not owners:
652 del index[path]
653
Brad Bishop63f59a72016-07-25 12:05:57 -0400654 def index_get_associations(self, path, owners=[], direction='forward'):
655 forward = 'forward' if direction == 'forward' else 'reverse'
656 reverse = 'reverse' if direction == 'forward' else 'forward'
657
658 associations = []
659 if not owners:
660 index = self.index.get(forward, {})
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930661 owners = index.get(path, {}).keys()
Brad Bishop63f59a72016-07-25 12:05:57 -0400662
663 # f: forward
664 # r: reverse
665 for rassoc in self.index_get(forward, path, owners):
666 elements = rassoc.split('/')
667 rtype = ''.join(elements[-1:])
668 fendpoint = '/'.join(elements[:-1])
669 for fassoc in self.index_get(reverse, fendpoint, owners):
670 elements = fassoc.split('/')
671 ftype = ''.join(elements[-1:])
672 rendpoint = '/'.join(elements[:-1])
673 if rendpoint != path:
674 continue
675 associations.append((ftype, rtype, fendpoint))
676
677 return associations
678
679 def update_association(self, path, removed, added):
680 iface = obmc.dbuslib.enums.OBMC_ASSOC_IFACE
Brad Bishop8e1f4ab2017-11-02 20:44:17 -0400681 assoc = self.manager.get(path, None)
Brad Bishop63f59a72016-07-25 12:05:57 -0400682
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930683 old_endpoints = set(assoc.Get(iface, 'endpoints') if assoc else [])
684 new_endpoints = old_endpoints.union(added).difference(removed)
Brad Bishop84041e32017-11-02 21:48:57 -0400685
686 if old_endpoints == new_endpoints:
687 return
688
689 create = [] if old_endpoints else [iface]
690 delete = [] if new_endpoints else [iface]
691
692 if create:
Brad Bishop63f59a72016-07-25 12:05:57 -0400693 self.manager.add(
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930694 path, Association(self.bus, path, list(new_endpoints)))
Brad Bishop84041e32017-11-02 21:48:57 -0400695 elif delete:
Brad Bishop63f59a72016-07-25 12:05:57 -0400696 self.manager.remove(path)
Brad Bishop84041e32017-11-02 21:48:57 -0400697 else:
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930698 assoc.Set(iface, 'endpoints', list(new_endpoints))
Brad Bishop63f59a72016-07-25 12:05:57 -0400699
700 if create != delete:
701 self.update_interfaces(
Brad Bishop57255f62018-01-29 15:26:06 -0500702 path, obmc.mapper.MAPPER_NAME, delete, create)
Brad Bishop63f59a72016-07-25 12:05:57 -0400703
704 def update_associations(
705 self, path, owner, old, new, created=[], destroyed=[]):
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930706 added = set(new).difference(old)
707 removed = set(old).difference(new)
Brad Bishop63f59a72016-07-25 12:05:57 -0400708 for forward, reverse, endpoint in added:
Brad Bishopb15b6312017-11-01 16:34:13 -0400709 if not endpoint:
710 # skip associations without an endpoint
711 continue
712
Brad Bishop63f59a72016-07-25 12:05:57 -0400713 # update the index
714 forward_path = str(path + '/' + forward)
715 reverse_path = str(endpoint + '/' + reverse)
716 self.index_append(
717 'forward', path, owner, reverse_path)
718 self.index_append(
719 'reverse', endpoint, owner, forward_path)
720
721 # create the association if the endpoint exists
722 if not self.cache_get(endpoint):
723 continue
724
725 self.update_association(forward_path, [], [endpoint])
726 self.update_association(reverse_path, [], [path])
727
728 for forward, reverse, endpoint in removed:
729 # update the index
730 forward_path = str(path + '/' + forward)
731 reverse_path = str(endpoint + '/' + reverse)
732 self.index_remove(
733 'forward', path, owner, reverse_path)
734 self.index_remove(
735 'reverse', endpoint, owner, forward_path)
736
737 # destroy the association if it exists
738 self.update_association(forward_path, [endpoint], [])
739 self.update_association(reverse_path, [path], [])
740
741 # If the associations interface endpoint comes
742 # or goes create or destroy the appropriate
743 # associations
744 for path in created:
745 for forward, reverse, endpoint in \
746 self.index_get_associations(path, direction='reverse'):
747 forward_path = str(path + '/' + forward)
748 reverse_path = str(endpoint + '/' + reverse)
749 self.update_association(forward_path, [], [endpoint])
750 self.update_association(reverse_path, [], [path])
751
752 for path in destroyed:
753 for forward, reverse, endpoint in \
754 self.index_get_associations(path, direction='reverse'):
755 forward_path = str(path + '/' + forward)
756 reverse_path = str(endpoint + '/' + reverse)
757 self.update_association(forward_path, [endpoint], [])
758 self.update_association(reverse_path, [path], [])
759
Brad Bishop1c33c222016-11-02 00:08:46 -0400760 @dbus.service.method(obmc.mapper.MAPPER_IFACE, 'sas', 'a{sa{sas}}')
761 def GetAncestors(self, path, interfaces):
Brad Bishop495ee092016-11-02 00:11:11 -0400762 if not self.cache_get(path):
763 raise MapperNotFoundException(path)
764
Brad Bishop63f59a72016-07-25 12:05:57 -0400765 objs = {}
Brad Bishop63f59a72016-07-25 12:05:57 -0400766
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930767 def parents(path):
768 yield "/"
769 parent = ""
770 for elem in (x for x in list(filter(bool, path.split('/')))[:-1]):
771 parent += "/" + elem
772 yield parent
773
774 for parent in parents(path):
775 obj = self.cache_get(parent)
Brad Bishop63f59a72016-07-25 12:05:57 -0400776 if not obj:
777 continue
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930778 objs[parent] = obj
Brad Bishop63f59a72016-07-25 12:05:57 -0400779
Andrew Jefferyb86b63a2018-05-09 16:10:46 +0930780 return self.filter_interfaces(objs.items(), interfaces)
Brad Bishop63f59a72016-07-25 12:05:57 -0400781
Brad Bishop829181d2017-02-24 09:49:14 -0500782 @dbus.service.signal(obmc.mapper.MAPPER_IFACE + '.Private', 's')
783 def IntrospectionComplete(self, name):
784 pass
785
Brad Bishop63f59a72016-07-25 12:05:57 -0400786
Brad Bishopcb2e1b32017-07-09 20:11:35 -0400787def server_main(
788 path_namespaces,
789 interface_namespaces,
790 blacklists,
791 interface_blacklists):
Brad Bishop63f59a72016-07-25 12:05:57 -0400792 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
793 bus = dbus.SystemBus()
Brad Bishopcb2e1b32017-07-09 20:11:35 -0400794 o = ObjectMapper(
795 bus,
796 obmc.mapper.MAPPER_PATH,
797 path_namespaces,
798 interface_namespaces,
799 blacklists,
800 interface_blacklists)
Brad Bishop63f59a72016-07-25 12:05:57 -0400801 loop = gobject.MainLoop()
802
803 loop.run()