blob: fda70c7c0b31bb60d351824aac784bf01a811470 [file] [log] [blame]
Brad Bishop732c6db2015-10-19 14:49:21 -04001#!/usr/bin/env python
2
3# Contributors Listed Below - COPYRIGHT 2015
4# [+] International Business Machines Corp.
5#
6#
7# Licensed under the Apache License, Version 2.0 (the "License");
8# you may not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS,
15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
16# implied. See the License for the specific language governing
17# permissions and limitations under the License.
18
19import sys
20import dbus
21import dbus.service
22import dbus.mainloop.glib
23import gobject
Brad Bishop3f43cdb2015-10-28 22:02:09 -040024from OpenBMCMapper import Path, IntrospectionParser
Brad Bishop732c6db2015-10-19 14:49:21 -040025import OpenBMCMapper
26
27class DictionaryCache:
28 def __init__(self):
29 self.items = {'/': {} }
30
31 def _merge_item(self, item):
32 path, bus, iface = item
33 if bus in self.items[path]:
34 self.items[path][bus].append(iface)
35 else:
36 self.items[path][bus] = [ iface ]
37
38 def add_item(self, item):
39 path, bus, iface = item
40 if path != '/':
41 parent = Path(path).fq(last = -1)
42 if parent not in self.items:
43 self.add_item((parent, None, None))
44
45 items = self.items.get(path, None)
46 if bus and items:
47 self._merge_item(item)
48 elif items:
49 pass
50 elif bus:
51 self.items[path] = { bus: [ iface ] }
52 else:
53 self.items[path] = {}
54
55 def add_items(self, items):
56 for i in items:
57 self.add_item(i)
58
59 def _has_children(self, path):
60 return len(self.get_subtree_paths(path, 1, 'fuzzy')) != 1
61
62 def _try_remove_path(self, path):
63 if path == '/':
64 return None
65
66 if not self._has_children(path):
67 del self.items[path]
68
69 # try and remove the parent
70 parent = Path(path).fq(last = -1)
71 if not self.items[parent]:
72 self._try_remove_path(parent)
73
74 def _remove_bus(self, path, bus):
75 del self.items[path][bus]
76 if not self.items[path]:
77 self._try_remove_path(path)
78
79 def _remove_iface(self, path, bus, iface):
80 self.items[path][bus].remove(iface)
81 if not self.items[path][bus]:
82 self._remove_bus(path, bus)
83
84 def remove_item(self, item):
85 self._remove_iface(*item)
86
87 def remove_items(self, items):
88 for x in items:
89 self.remove_item(x)
90
91 def remove_bus(self, name):
92 for path,x in self.items.items():
93 for bus,ifaces in x.items():
94 if name != bus:
95 continue
96 self.remove_items([ (path, bus, iface) \
97 for iface in ifaces ])
98
99 def remove_busses(self, names):
100 for x in names:
101 self.remove_bus(name)
102
103 def _get_busses(self):
104 busses = [ y.iterkeys() for y in [ x for x in self.items.itervalues() ] ]
105 return set( x for y in busses for x in y ) # flatten nested lists
106
107 def has_bus(self, bus):
108 return any(bus in b for b in self._get_busses())
109
110 def get_subtree_paths(self, root, depth, match_type):
111 return filter(PathMatch(root, depth, match_type),
112 self.items.iterkeys())
113
114 def get_subtree(self, root, depth, match_type):
115 return dict(filter(ObjectMatch(root, depth, match_type),
116 self.items.iteritems()))
117
118 @staticmethod
119 def _iface__str__(ifaces):
120 return '\n'.join([" %s" %(x) for x in ifaces ])
121
122 @staticmethod
123 def _bus__str__(busses):
124 return '\n'.join([ "%s\n%s" %(x, DictionaryCache._iface__str__(y)) \
125 for x,y in busses.iteritems() ])
126
127 def __str__(self):
128 return '\n'.join([ "%s\n%s" %(x, DictionaryCache._bus__str__(y)) \
129 for x,y in self.items.iteritems() ])
130
131class PathMatch(object):
132 def __init__(self, path, depth, match_type):
133 p = Path(path)
134 self.path = p.fq()
135 self.depth = p.depth()
136 self.match_type = match_type
137 self.match_depth = None
138 if depth != -1:
139 self.match_depth = p.depth() + depth
140
141 def __call__(self, path):
142 p = Path(path)
143 depth = p.depth()
144
145 # 'is not None' is really needed because
146 # the match depth can be zero
147 if self.match_depth is not None and depth > self.match_depth:
148 return False
149
150 fuzzy_match = p.fq(last = self.depth) == self.path or \
151 not self.depth
152 depth_match = self.match_depth == depth
153
154 if self.match_type == 'fuzzy':
155 return fuzzy_match
156
157 return fuzzy_match and depth_match
158
159class ObjectMatch(PathMatch):
160 def __init__(self, path, depth, match_type):
161 super(ObjectMatch, self).__init__(path, depth, match_type)
162
163 def __call__(self, tup):
164 if not super(ObjectMatch, self).__call__(tup[0]):
165 return False
166 return tup[1]
167
168class ObjectMapper(dbus.service.Object):
169 def __init__(self, bus, path,
Brad Bishop6fb84612015-11-01 00:06:24 -0400170 name_match = OpenBMCMapper.org_dot_openbmc_match,
171 intf_match = OpenBMCMapper.org_dot_openbmc_match):
Brad Bishop732c6db2015-10-19 14:49:21 -0400172 super(ObjectMapper, self).__init__(bus.dbus, path)
173 self.cache = DictionaryCache()
174 self.bus = bus
175 self.name_match = name_match
176 self.intf_match = intf_match
Brad Bishop6fb84612015-11-01 00:06:24 -0400177 self.tag_match = OpenBMCMapper.TagListMatch(['children', 'interface'])
178
Brad Bishop732c6db2015-10-19 14:49:21 -0400179 self.discovery_done = False
180
181 gobject.idle_add(self.discover)
182 self.bus.dbus.add_signal_receiver(self.bus_handler,
183 dbus_interface = dbus.BUS_DAEMON_IFACE,
184 signal_name = 'NameOwnerChanged')
185 self.bus.dbus.add_signal_receiver(self.interfaces_added_handler,
186 dbus_interface = dbus.BUS_DAEMON_IFACE + '.ObjectManager',
187 signal_name = 'InterfaceesAdded',
188 sender_keyword = 'sender')
189 self.bus.dbus.add_signal_receiver(self.interfaces_removed_handler,
190 dbus_interface = dbus.BUS_DAEMON_IFACE + '.ObjectManager',
191 signal_name = 'InterfacesRemoved',
192 sender_keyword = 'sender')
193
194 def interfaces_added_handler(self, path, iprops, **kw):
195 for x in iprops.iterkeys():
Brad Bishop6fb84612015-11-01 00:06:24 -0400196 if self.intf_match(x):
Brad Bishop732c6db2015-10-19 14:49:21 -0400197 self.cache.add_item((path, kw['sender'], x))
198
199 def interfaces_removed_handler(self, path, interfaces, **kw):
200 for x in interfaces:
201 self.cache.remove_item((path, kw['sender'], x))
202
203 def process_new_owner(self, name):
204 # unique name
Brad Bishop6fb84612015-11-01 00:06:24 -0400205 return self.discover([ IntrospectionParser(name,
206 self.bus.dbus, self.tag_match, self.intf_match) ])
Brad Bishop732c6db2015-10-19 14:49:21 -0400207
208 def process_old_owner(self, name):
209 # unique name
210 self.cache.remove_bus(name)
211
212 def bus_handler(self, service, old, new):
213 if not self.discovery_done or \
Brad Bishop6fb84612015-11-01 00:06:24 -0400214 not self.name_match(service):
Brad Bishop732c6db2015-10-19 14:49:21 -0400215 return
216
217 if new:
218 self.process_new_owner(new)
219 if old:
220 self.process_old_owner(old)
221
Brad Bishop6fb84612015-11-01 00:06:24 -0400222 def add_interfaces(self, owner, path, interfaces):
Brad Bishop86398d22015-10-28 21:58:31 -0400223 for x in interfaces:
Brad Bishop86398d22015-10-28 21:58:31 -0400224 self.cache.add_item((path, owner, x))
225
Brad Bishop6fb84612015-11-01 00:06:24 -0400226 def add_items(self, owner, bus_items):
Brad Bishop86398d22015-10-28 21:58:31 -0400227 for x,y in bus_items.iteritems():
Brad Bishop6fb84612015-11-01 00:06:24 -0400228 self.add_interfaces(owner, x, y['interfaces'])
Brad Bishop86398d22015-10-28 21:58:31 -0400229
Brad Bishop732c6db2015-10-19 14:49:21 -0400230 def discover(self, owners = None):
231 discovery = not self.discovery_done
232 if not owners:
Brad Bishop6fb84612015-11-01 00:06:24 -0400233 owners = [ IntrospectionParser(x, self.bus.dbus,
234 self.tag_match, self.intf_match) \
235 for x in self.bus.get_owner_names(self.name_match) ]
Brad Bishop732c6db2015-10-19 14:49:21 -0400236 self.discovery_done = True
237 for o in owners:
238
239 # this only happens when an app
240 # grabs more than one well known name
241 if self.cache.has_bus(o.name):
242 continue
243
Brad Bishop6fb84612015-11-01 00:06:24 -0400244 self.add_items(o.name, o.introspect())
Brad Bishop732c6db2015-10-19 14:49:21 -0400245
246 if discovery:
247 print "ObjectMapper discovery complete..."
248
249 @dbus.service.method(OpenBMCMapper.MAPPER_IFACE, 'sis', 'as')
250 def GetTreePaths(self, path, depth, match_type):
251 return self.cache.get_subtree_paths(path, depth, match_type)
252
253 @dbus.service.method(OpenBMCMapper.MAPPER_IFACE, 'sis', 'a{sa{sas}}')
254 def GetTree(self, path, depth, match_type):
255 return self.cache.get_subtree(path, depth, match_type)
256
257 @dbus.service.method(OpenBMCMapper.MAPPER_IFACE, 'asis', 'a{sa{sas}}')
258 def GetTrees(self, paths, depth, match_type):
259 values = {}
260 for x in paths:
261 values.update(self.GetTree(x, depth, match_type))
262 return values
263
Brad Bishop732c6db2015-10-19 14:49:21 -0400264class BusWrapper:
265 def __init__(self, bus):
266 self.dbus = bus
267
268 def get_service_names(self, match):
269 # these are well known names
270 return [ x for x in self.dbus.list_names() \
Brad Bishop6fb84612015-11-01 00:06:24 -0400271 if match(x) and x != OpenBMCMapper.MAPPER_NAME ]
Brad Bishop732c6db2015-10-19 14:49:21 -0400272
273 def get_owner_names(self, match):
274 # these are unique connection names
275 return list( set( [ self.dbus.get_name_owner(x) \
276 for x in self.get_service_names(match) ] ) )
277
Brad Bishop732c6db2015-10-19 14:49:21 -0400278if __name__ == '__main__':
279 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
280 bus = dbus.SystemBus()
281 s = dbus.service.BusName(OpenBMCMapper.MAPPER_NAME, bus)
282 o = ObjectMapper(BusWrapper(bus), OpenBMCMapper.MAPPER_PATH)
283 loop = gobject.MainLoop()
284
285 loop.run()