Brad Bishop | 732c6db | 2015-10-19 14:49:21 -0400 | [diff] [blame] | 1 | #!/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 | |
Brad Bishop | 3f43cdb | 2015-10-28 22:02:09 -0400 | [diff] [blame] | 19 | from xml.etree import ElementTree |
| 20 | import dbus |
| 21 | |
Brad Bishop | 732c6db | 2015-10-19 14:49:21 -0400 | [diff] [blame] | 22 | MAPPER_NAME = 'org.openbmc.objectmapper' |
| 23 | MAPPER_IFACE = MAPPER_NAME + '.ObjectMapper' |
| 24 | MAPPER_PATH = '/org/openbmc/objectmapper/objectmapper' |
Brad Bishop | d4a1b93 | 2015-11-03 14:43:35 -0500 | [diff] [blame] | 25 | ENUMERATE_IFACE = 'org.openbmc.Object.Enumerate' |
Brad Bishop | 732c6db | 2015-10-19 14:49:21 -0400 | [diff] [blame] | 26 | |
Brad Bishop | 3640ecd | 2015-11-03 14:46:10 -0500 | [diff] [blame] | 27 | class MapperNotFoundException(Exception): |
| 28 | def __init__(self, msg): |
| 29 | super(MapperNotFoundException, self).__init__(msg) |
| 30 | |
Brad Bishop | 732c6db | 2015-10-19 14:49:21 -0400 | [diff] [blame] | 31 | class Path: |
| 32 | def __init__(self, path): |
| 33 | self.parts = filter(bool, path.split('/')) |
| 34 | |
| 35 | def rel(self, first = None, last = None): |
| 36 | # relative |
| 37 | return self.get('', first, last) |
| 38 | |
| 39 | def fq(self, first = None, last = None): |
| 40 | # fully qualified |
| 41 | return self.get('/', first, last) |
| 42 | |
| 43 | def depth(self): |
| 44 | return len(self.parts) |
| 45 | |
| 46 | def get(self, prefix = '/', first = None, last = None): |
| 47 | if not first: |
| 48 | first = 0 |
| 49 | if not last: |
| 50 | last = self.depth() |
| 51 | return prefix + '/'.join(self.parts[first:last]) |
Brad Bishop | 3f43cdb | 2015-10-28 22:02:09 -0400 | [diff] [blame] | 52 | |
Brad Bishop | 6fb8461 | 2015-11-01 00:06:24 -0400 | [diff] [blame] | 53 | def org_dot_openbmc_match(name): |
| 54 | return 'org.openbmc' in name |
| 55 | |
Brad Bishop | a8e752f | 2015-11-03 09:24:48 -0500 | [diff] [blame] | 56 | class ListMatch(object): |
| 57 | def __init__(self, l): |
| 58 | self.l = l |
Brad Bishop | 6fb8461 | 2015-11-01 00:06:24 -0400 | [diff] [blame] | 59 | |
Brad Bishop | a8e752f | 2015-11-03 09:24:48 -0500 | [diff] [blame] | 60 | def __call__(self, match): |
| 61 | return match in self.l |
Brad Bishop | 6fb8461 | 2015-11-01 00:06:24 -0400 | [diff] [blame] | 62 | |
Brad Bishop | 3f43cdb | 2015-10-28 22:02:09 -0400 | [diff] [blame] | 63 | class IntrospectionNodeParser: |
Brad Bishop | 6fb8461 | 2015-11-01 00:06:24 -0400 | [diff] [blame] | 64 | def __init__(self, data, tag_match = bool, intf_match = bool): |
Brad Bishop | 3f43cdb | 2015-10-28 22:02:09 -0400 | [diff] [blame] | 65 | self.data = data |
| 66 | self.cache = {} |
Brad Bishop | 6fb8461 | 2015-11-01 00:06:24 -0400 | [diff] [blame] | 67 | self.tag_match = tag_match |
| 68 | self.intf_match = intf_match |
Brad Bishop | 3f43cdb | 2015-10-28 22:02:09 -0400 | [diff] [blame] | 69 | |
| 70 | def parse_args(self): |
| 71 | return [ x.attrib for x in self.data.findall('arg') ] |
| 72 | |
| 73 | def parse_children(self): |
| 74 | return [ x.attrib['name'] for x in self.data.findall('node') ] |
| 75 | |
| 76 | def parse_method_or_signal(self): |
| 77 | name = self.data.attrib['name'] |
| 78 | return name, self.parse_args() |
| 79 | |
| 80 | def parse_interface(self): |
| 81 | iface = {} |
| 82 | iface['method'] = {} |
| 83 | iface['signal'] = {} |
Brad Bishop | 3f43cdb | 2015-10-28 22:02:09 -0400 | [diff] [blame] | 84 | |
| 85 | for node in self.data: |
Brad Bishop | 3f43cdb | 2015-10-28 22:02:09 -0400 | [diff] [blame] | 86 | if node.tag not in ['method', 'signal']: |
| 87 | continue |
Brad Bishop | 6fb8461 | 2015-11-01 00:06:24 -0400 | [diff] [blame] | 88 | if not self.tag_match(node.tag): |
| 89 | continue |
| 90 | p = IntrospectionNodeParser( |
| 91 | node, self.tag_match, self.intf_match) |
Brad Bishop | 3f43cdb | 2015-10-28 22:02:09 -0400 | [diff] [blame] | 92 | n, element = p.parse_method_or_signal() |
| 93 | iface[node.tag][n] = element |
| 94 | |
Brad Bishop | 6fb8461 | 2015-11-01 00:06:24 -0400 | [diff] [blame] | 95 | return iface |
Brad Bishop | 3f43cdb | 2015-10-28 22:02:09 -0400 | [diff] [blame] | 96 | |
| 97 | def parse_node(self): |
| 98 | if self.cache: |
| 99 | return self.cache |
| 100 | |
| 101 | self.cache['interfaces'] = {} |
| 102 | self.cache['children'] = [] |
| 103 | |
| 104 | for node in self.data: |
Brad Bishop | 3f43cdb | 2015-10-28 22:02:09 -0400 | [diff] [blame] | 105 | if node.tag == 'interface': |
Brad Bishop | 6fb8461 | 2015-11-01 00:06:24 -0400 | [diff] [blame] | 106 | p = IntrospectionNodeParser( |
| 107 | node, self.tag_match, self.intf_match) |
| 108 | name = p.data.attrib['name'] |
| 109 | if not self.intf_match(name): |
| 110 | continue |
| 111 | self.cache['interfaces'][name] = p.parse_interface() |
Brad Bishop | 3f43cdb | 2015-10-28 22:02:09 -0400 | [diff] [blame] | 112 | elif node.tag == 'node': |
| 113 | self.cache['children'] = self.parse_children() |
| 114 | |
| 115 | return self.cache |
| 116 | |
| 117 | def get_interfaces(self): |
| 118 | return self.parse_node()['interfaces'] |
| 119 | |
| 120 | def get_children(self): |
| 121 | return self.parse_node()['children'] |
| 122 | |
| 123 | def recursive_binding(self): |
| 124 | return any('/' in s for s in self.get_children()) |
| 125 | |
| 126 | class IntrospectionParser: |
Brad Bishop | 6fb8461 | 2015-11-01 00:06:24 -0400 | [diff] [blame] | 127 | def __init__(self, name, bus, tag_match = bool, intf_match = bool): |
Brad Bishop | 3f43cdb | 2015-10-28 22:02:09 -0400 | [diff] [blame] | 128 | self.name = name |
| 129 | self.bus = bus |
Brad Bishop | 6fb8461 | 2015-11-01 00:06:24 -0400 | [diff] [blame] | 130 | self.tag_match = tag_match |
| 131 | self.intf_match = intf_match |
Brad Bishop | 3f43cdb | 2015-10-28 22:02:09 -0400 | [diff] [blame] | 132 | |
| 133 | def _introspect(self, path): |
| 134 | try: |
Brad Bishop | 65b7ceb | 2015-11-04 23:05:56 -0500 | [diff] [blame] | 135 | obj = self.bus.get_object(self.name, path, introspect = False) |
Brad Bishop | 70f7f56 | 2015-11-04 23:07:42 -0500 | [diff] [blame] | 136 | iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE) |
Brad Bishop | 3f43cdb | 2015-10-28 22:02:09 -0400 | [diff] [blame] | 137 | data = iface.Introspect() |
| 138 | except dbus.DBusException: |
| 139 | return None |
| 140 | |
Brad Bishop | 6fb8461 | 2015-11-01 00:06:24 -0400 | [diff] [blame] | 141 | return IntrospectionNodeParser( |
| 142 | ElementTree.fromstring(data), |
| 143 | self.tag_match, |
| 144 | self.intf_match) |
Brad Bishop | 3f43cdb | 2015-10-28 22:02:09 -0400 | [diff] [blame] | 145 | |
| 146 | def _discover_flat(self, path, parser): |
| 147 | items = {} |
| 148 | interfaces = parser.get_interfaces().keys() |
| 149 | if interfaces: |
| 150 | items[path] = {} |
| 151 | items[path]['interfaces'] = interfaces |
| 152 | |
| 153 | return items |
| 154 | |
| 155 | def introspect(self, path = '/', parser = None): |
| 156 | items = {} |
| 157 | if not parser: |
| 158 | parser = self._introspect(path) |
| 159 | if not parser: |
| 160 | return {} |
| 161 | items.update(self._discover_flat(path, parser)) |
| 162 | |
| 163 | if path != '/': |
| 164 | path += '/' |
| 165 | |
| 166 | if parser.recursive_binding(): |
| 167 | callback = self._discover_flat |
| 168 | else: |
| 169 | callback = self.introspect |
| 170 | |
| 171 | for k in parser.get_children(): |
| 172 | parser = self._introspect(path + k) |
| 173 | if not parser: |
| 174 | continue |
| 175 | items.update(callback(path + k, parser)) |
| 176 | |
| 177 | return items |
Brad Bishop | e318ec2 | 2015-11-03 09:12:52 -0500 | [diff] [blame] | 178 | |
| 179 | class PathTreeItemIterator(object): |
| 180 | def __init__(self, path_tree, subtree, depth): |
| 181 | self.path_tree = path_tree |
| 182 | self.path = [] |
| 183 | self.itlist = [] |
| 184 | self.subtree = ['/'] + filter(bool, subtree.split('/')) |
| 185 | self.depth = depth |
| 186 | d = path_tree.root |
| 187 | for k in self.subtree: |
| 188 | try: |
| 189 | d = d[k]['children'] |
| 190 | except KeyError: |
| 191 | raise KeyError(subtree) |
| 192 | self.it = d.iteritems() |
| 193 | |
| 194 | def __iter__(self): |
| 195 | return self |
| 196 | |
| 197 | def __next__(self): |
| 198 | return super(PathTreeItemIterator, self).next() |
| 199 | |
| 200 | def next(self): |
| 201 | key, value = self._next() |
| 202 | path = self.subtree[0] + '/'.join(self.subtree[1:] + self.path) |
| 203 | return path, value.get('data') |
| 204 | |
| 205 | def _next(self): |
| 206 | try: |
| 207 | while True: |
| 208 | x = self.it.next() |
| 209 | depth_exceeded = len(self.path) +1 > self.depth |
| 210 | if self.depth and depth_exceeded: |
| 211 | continue |
| 212 | self.itlist.append(self.it) |
| 213 | self.path.append(x[0]) |
| 214 | self.it = x[1]['children'].iteritems() |
| 215 | break; |
| 216 | |
| 217 | except StopIteration: |
| 218 | if not self.itlist: |
| 219 | raise StopIteration |
| 220 | |
| 221 | self.it = self.itlist.pop() |
| 222 | self.path.pop() |
| 223 | x = self._next() |
| 224 | |
| 225 | return x |
| 226 | |
| 227 | class PathTreeKeyIterator(PathTreeItemIterator): |
| 228 | def __init__(self, path_tree, subtree, depth): |
| 229 | super(PathTreeKeyIterator, self).__init__(path_tree, subtree, depth) |
| 230 | |
| 231 | def next(self): |
| 232 | return super(PathTreeKeyIterator, self).next()[0] |
| 233 | |
| 234 | class PathTree: |
| 235 | def __init__(self): |
| 236 | self.root = {} |
| 237 | |
| 238 | def _try_delete_parent(self, elements): |
| 239 | if len(elements) == 1: |
| 240 | return False |
| 241 | |
| 242 | kids = 'children' |
| 243 | elements.pop() |
| 244 | d = self.root |
| 245 | for k in elements[:-1]: |
| 246 | d = d[k][kids] |
| 247 | |
| 248 | if 'data' not in d[elements[-1]] and not d[elements[-1]][kids]: |
| 249 | del d[elements[-1]] |
| 250 | self._try_delete_parent(elements) |
| 251 | |
| 252 | def _get_node(self, key): |
| 253 | kids = 'children' |
| 254 | elements = ['/'] + filter(bool, key.split('/')) |
| 255 | d = self.root |
| 256 | for k in elements[:-1]: |
| 257 | try: |
| 258 | d = d[k][kids] |
| 259 | except KeyError: |
| 260 | raise KeyError(key) |
| 261 | |
| 262 | return d[elements[-1]] |
| 263 | |
| 264 | def __iter__(self): |
| 265 | return self |
| 266 | |
| 267 | def __missing__(self, key): |
| 268 | for x in self.iterkeys(): |
| 269 | if key == x: |
| 270 | return False |
| 271 | return True |
| 272 | |
| 273 | def __delitem__(self, key): |
| 274 | kids = 'children' |
| 275 | elements = ['/'] + filter(bool, key.split('/')) |
| 276 | d = self.root |
| 277 | for k in elements[:-1]: |
| 278 | try: |
| 279 | d = d[k][kids] |
| 280 | except KeyError: |
| 281 | raise KeyError(key) |
| 282 | |
| 283 | del d[elements[-1]] |
| 284 | self._try_delete_parent(elements) |
| 285 | |
| 286 | def __setitem__(self, key, value): |
| 287 | kids = 'children' |
| 288 | elements = ['/'] + filter(bool, key.split('/')) |
| 289 | d = self.root |
| 290 | for k in elements[:-1]: |
| 291 | d = d.setdefault(k, {kids: {}})[kids] |
| 292 | |
| 293 | children = d.setdefault(elements[-1], {kids: {}})[kids] |
| 294 | d[elements[-1]].update({kids: children, 'data': value}) |
| 295 | |
| 296 | def __getitem__(self, key): |
| 297 | return self._get_node(key).get('data') |
| 298 | |
| 299 | def setdefault(self, key, default): |
| 300 | if not self.get(key): |
| 301 | self.__setitem__(key, default) |
| 302 | |
| 303 | return self.__getitem__(key) |
| 304 | |
| 305 | def get(self, key, default = None): |
| 306 | try: |
| 307 | x = self.__getitem__(key) |
| 308 | except KeyError: |
| 309 | x = default |
| 310 | |
| 311 | return x |
| 312 | |
| 313 | def get_children(self, key): |
| 314 | return [ x for x in self._get_node(key)['children'].iterkeys() ] |
| 315 | |
| 316 | def demote(self, key): |
| 317 | n = self._get_node(key) |
| 318 | if 'data' in n: |
| 319 | del n['data'] |
| 320 | |
| 321 | def keys(self, subtree = '/', depth = None): |
| 322 | return [ x for x in self.iterkeys(subtree, depth) ] |
| 323 | |
| 324 | def values(self, subtree = '/', depth = None): |
| 325 | return [ x[1] for x in self.iteritems(subtree, depth) ] |
| 326 | |
| 327 | def items(self, subtree = '/', depth = None): |
| 328 | return [ x for x in self.iteritems(subtree, depth) ] |
| 329 | |
| 330 | def dataitems(self, subtree = '/', depth = None): |
| 331 | return [ x for x in self.iteritems(subtree, depth) \ |
| 332 | if x[1] is not None ] |
| 333 | |
| 334 | def iterkeys(self, subtree = '/', depth = None): |
| 335 | if not self.root: |
| 336 | return {}.iterkeys() |
| 337 | return PathTreeKeyIterator(self, subtree, depth) |
| 338 | |
| 339 | def iteritems(self, subtree = '/', depth = None): |
| 340 | if not self.root: |
| 341 | return {}.iteritems() |
| 342 | return PathTreeItemIterator(self, subtree, depth) |
Brad Bishop | 77fc9df | 2015-11-03 14:45:07 -0500 | [diff] [blame] | 343 | |
| 344 | class Mapper: |
| 345 | def __init__(self, bus): |
| 346 | self.bus = bus |
Brad Bishop | 65b7ceb | 2015-11-04 23:05:56 -0500 | [diff] [blame] | 347 | obj = bus.get_object(MAPPER_NAME, MAPPER_PATH, introspect = False) |
Brad Bishop | 77fc9df | 2015-11-03 14:45:07 -0500 | [diff] [blame] | 348 | self.iface = dbus.Interface( |
| 349 | obj, dbus_interface = MAPPER_IFACE) |
| 350 | |
| 351 | def get_object(self, path): |
| 352 | return self.iface.GetObject(path) |
| 353 | |
| 354 | def get_subtree_paths(self, path = '/', depth = 0): |
| 355 | return self.iface.GetSubTreePaths(path, depth) |
| 356 | |
| 357 | def get_subtree(self, path = '/', depth = 0): |
| 358 | return self.iface.GetSubTree(path, depth) |