def list_sensors(self): try: sensors = self.ipmicmd.get_sensor_descriptions() except pygexc.IpmiException: self.output.put(msg.ConfluentTargetTimeout(self.node)) return self.output.put(msg.ChildCollection('all')) for sensor in filter(self.match_sensor, sensors): self.output.put(msg.ChildCollection(simplify_name(sensor['name'])))
def retrieve_inventory(configmanager, creds, node, results, element): if len(element) == 3: results.put(msg.ChildCollection('all')) results.put(msg.ChildCollection('system')) return wc = WebClient(node, configmanager, creds) invinfo = wc.fetch('/affluent/inventory/hardware/all', results) if invinfo: results.put(msg.KeyValueData(invinfo, node))
def list_inventory(self): try: components = self.ipmicmd.get_inventory_descriptions() except pygexc.IpmiException: self.output.put(msg.ConfluentTargetTimeout(self.node)) return self.output.put(msg.ChildCollection('all')) for component in components: self.output.put(msg.ChildCollection(simplify_name(component)))
def handle_users(self): # Create user if len(self.element) == 3: if self.op == 'update': user = self.inputdata.credentials[self.node] self.ipmicmd.create_user(uid=user['uid'], name=user['username'], password=user['password'], callback=True,link_auth=True, ipmi_msg=True, privilege_level=user['privilege_level']) # A list of users self.output.put(msg.ChildCollection('all')) for user in self.ipmicmd.get_users(): self.output.put(msg.ChildCollection(user, candelete=True)) return # List all users elif len(self.element) == 4 and self.element[-1] == 'all': users = [] for user in self.ipmicmd.get_users(): users.append(self.ipmicmd.get_user(uid=user)) self.output.put(msg.UserCollection(users=users, name=self.node)) return # Update user elif len(self.element) == 4: user = int(self.element[-1]) if self.op == 'read': data = self.ipmicmd.get_user(uid=user) self.output.put(msg.User( uid=data['uid'], username=data['name'], privilege_level=data['access']['privilege_level'], name=self.node)) return elif self.op == 'update': user = self.inputdata.credentials[self.node] if 'username' in user: self.ipmicmd.set_user_name(uid=user['uid'], name=user['username']) if 'privilege_level' in user: self.ipmicmd.set_user_access(uid=user['uid'], privilege_level=user['privilege_level']) if 'password' in user: self.ipmicmd.set_user_password(uid=user['uid'], password=user['password']) self.ipmicmd.set_user_password(uid=user['uid'], mode='enable', password=user['password']) if 'enabled' in user: if user['enabled'] == 'yes': mode = 'enable' else: mode = 'disable' self.ipmicmd.disable_user(user['uid'], mode) return elif self.op == 'delete': self.ipmicmd.user_delete(uid=user) return
def handle_deployment(configmanager, inputdata, pathcomponents, operation): if len(pathcomponents) == 1: yield msg.ChildCollection('distributions/') yield msg.ChildCollection('profiles/') yield msg.ChildCollection('importing/') return if pathcomponents[1] == 'distributions': if len(pathcomponents) == 2 and operation == 'retrieve': for dist in osimage.list_distros(): yield msg.ChildCollection(dist + '/') return if len(pathcomponents) == 3: distname = pathcomponents[-1] if 'operation' == 'update': if inputdata.get('rescan', False): osimage.rescan_dist(distname) if pathcomponents[1] == 'profiles': if len(pathcomponents) == 2 and operation == 'retrieve': for prof in osimage.list_profiles(): yield msg.ChildCollection(prof + '/') return if len(pathcomponents) == 3: profname = pathcomponents[-1] if operation == 'update' and 'updateboot' in inputdata: osimage.update_boot(profname) yield msg.KeyValueData({'updated': profname}) return if pathcomponents[1] == 'importing': if len(pathcomponents) == 2 or not pathcomponents[-1]: if operation == 'retrieve': for imp in osimage.list_importing(): yield imp return elif operation == 'create': importer = osimage.MediaImporter(inputdata['filename'], configmanager) yield msg.KeyValueData({ 'target': importer.targpath, 'name': importer.importkey }) return elif len(pathcomponents) == 3: if operation == 'retrieve': for res in osimage.get_importing_status(pathcomponents[-1]): yield res return elif operation == 'delete': for res in osimage.remove_importing(pathcomponents[-1]): yield res return raise exc.NotFoundException('Unrecognized request')
def handle_read_api_request(pathcomponents): # TODO(jjohnson2): This should be more generalized... # odd indexes into components are 'by-'*, even indexes # starting at 2 are parameters to previous index subcats, queryparms, indexof, coll = _parameterize_path(pathcomponents[1:]) if len(pathcomponents) == 1: dirlist = [msg.ChildCollection(x + '/') for x in sorted(list(subcats))] dirlist.append(msg.ChildCollection('rescan')) return dirlist if not coll: return show_info(queryparms['by-mac']) if not indexof: return [msg.ChildCollection(x + '/') for x in sorted(list(subcats))] if indexof not in list_info: raise exc.NotFoundException('{0} is not found'.format(indexof)) return list_info[indexof](queryparms)
def list_matching_uuids(criteria): for uuid in sorted(list(known_uuids)): for mac in known_uuids[uuid]: info = known_uuids[uuid][mac] if _info_matches(info, criteria): yield msg.ChildCollection(uuid + '/') break
def list_info(parms, requestedparameter): #{u'by-switch': u'r8e1', u'by-port': u'e'} #by-peerport suffix = '/' if requestedparameter in multi_selectors else '' results = set([]) requestedparameter = requestedparameter.replace('by-', '') for info in _neighbypeerid: if info == '!!vintage': continue info = _neighbypeerid[info] for mk in parms: mk = mk.replace('by-', '') if mk not in info: continue if (not close_enough(parms['by-' + mk], info[mk]) or requestedparameter not in info): break else: candidate = info[requestedparameter] candidate = candidate.strip() if candidate != '': results.add(_api_sanitize_string(candidate)) return [ msg.ChildCollection(x + suffix) for x in util.natural_sort(results) ]
def handle_nets(self): if len(self.element) == 3: if self.op != 'read': self.output.put( msg.ConfluentNodeError(self.node, 'Unsupported operation')) return self.output.put(msg.ChildCollection('management')) elif len(self.element) == 4 and self.element[-1] == 'management': if self.op == 'read': lancfg = self.ipmicmd.get_net_configuration() self.output.put(msg.NetworkConfiguration( self.node, ipv4addr=lancfg['ipv4_address'], ipv4gateway=lancfg['ipv4_gateway'], ipv4cfgmethod=lancfg['ipv4_configuration'], hwaddr=lancfg['mac_address'] )) elif self.op == 'update': config = self.inputdata.netconfig(self.node) try: self.ipmicmd.set_net_configuration( ipv4_address=config['ipv4_address'], ipv4_configuration=config['ipv4_configuration'], ipv4_gateway=config['ipv4_gateway']) except socket.error as se: self.output.put(msg.ConfluentNodeError(self.node, se.message)) except ValueError as e: if e.message == 'negative shift count': self.output.put(msg.ConfluentNodeError( self.node, 'Invalid prefix length given')) else: raise
def iterate_resources(fancydict): for resource in fancydict.iterkeys(): if resource.startswith("_"): continue if not isinstance(fancydict[resource], PluginRoute): # a resource resource += '/' yield msg.ChildCollection(resource)
def list_updates(nodes, tenant, element, type='firmware'): showmode = False if type == 'mediaupload': myparty = uploadsbytarget verb = 'upload' else: myparty = updatesbytarget verb = 'update' if type == 'firmware': specificlen = 4 else: specificlen = 2 if len(element) > specificlen: showmode = True upid = element[-1] for node in nodes: if showmode: try: updater = myparty[(node, tenant)][upid] except KeyError: raise exc.NotFoundException( 'No matching {0} process found'.format(verb)) yield msg.KeyValueData(updater.progress, name=node) else: for updateid in myparty.get((node, tenant), {}): yield msg.ChildCollection(updateid)
def handle_ntp(self): if self.element[3] == 'enabled': if 'read' == self.op: enabled = self.ipmicmd.get_ntp_enabled() self.output.put(msg.NTPEnabled(self.node, enabled)) return elif 'update' == self.op: enabled = self.inputdata.ntp_enabled(self.node) self.ipmicmd.set_ntp_enabled(enabled == 'True') return elif self.element[3] == 'servers': if len(self.element) == 4: self.output.put(msg.ChildCollection('all')) size = len(self.ipmicmd.get_ntp_servers()) for idx in range(1, size + 1): self.output.put(msg.ChildCollection(idx)) else: if 'read' == self.op: if self.element[-1] == 'all': servers = self.ipmicmd.get_ntp_servers() self.output.put(msg.NTPServers(self.node, servers)) return else: idx = int(self.element[-1]) - 1 servers = self.ipmicmd.get_ntp_servers() if len(servers) > idx: self.output.put( msg.NTPServer(self.node, servers[idx])) else: self.output.put( msg.ConfluentTargetNotFound( self.node, 'Requested NTP configuration not found')) return elif self.op in ('update', 'create'): if self.element[-1] == 'all': servers = self.inputdata.ntp_servers(self.node) for idx in servers: self.ipmicmd.set_ntp_server( servers[idx], int(idx[-1]) - 1) return else: idx = int(self.element[-1]) - 1 server = self.inputdata.ntp_server(self.node) self.ipmicmd.set_ntp_server(server, idx) return
def retrieve_firmware(configmanager, creds, node, results, element): if len(element) == 3: results.put(msg.ChildCollection('all')) return wc = WebClient(node, configmanager, creds) fwinfo = wc.fetch('/affluent/inventory/firmware/all', results) if fwinfo: results.put(msg.Firmware(fwinfo, node))
def iterate_resources(fancydict): for resource in fancydict: if resource.startswith("_"): continue if resource == 'abbreviate': pass elif not isinstance(fancydict[resource], PluginRoute): # a resource resource += '/' yield msg.ChildCollection(resource)
def list_matching_types(criteria): rettypes = [] for infotype in known_services: typename = servicenames[infotype] if ('by-model' not in criteria or criteria['by-model'] in known_services[infotype]): rettypes.append(typename) return [msg.ChildCollection(typename + '/') for typename in sorted(rettypes)]
def list_matching_nodes(criteria): retnodes = [] for node in known_nodes: for mac in known_nodes[node]: info = known_info[mac] if _info_matches(info, criteria): retnodes.append(node) break retnodes.sort(key=noderange.humanify_nodename) return [msg.ChildCollection(node + '/') for node in retnodes]
def retrieve_inventory(configmanager, creds, node, results, element): if len(element) == 3: results.put(msg.ChildCollection('all')) results.put(msg.ChildCollection('system')) return wc = cnos_login(node, configmanager, creds) sysinfo = wc.grab_json_response('/nos/api/sysinfo/inventory') invinfo = { 'inventory': [{ 'name': 'System', 'present': True, 'information': { 'Product name': sysinfo['Model'], 'Serial Number': sysinfo['Electronic Serial Number'], 'Board Serial Number': sysinfo['Serial Number'], 'Manufacturer': 'Lenovo', 'Model': sysinfo['Machine Type Model'], 'FRU Number': sysinfo['FRU'].strip(), } }] } results.put(msg.KeyValueData(invinfo, node))
def list_updates(nodes, tenant, element): showmode = False if len(element) > 4: showmode = True upid = element[-1] for node in nodes: if showmode: try: updater = updatesbytarget[(node, tenant)][upid] except KeyError: raise exc.NotFoundException('No matching update process found') yield msg.KeyValueData(updater.progress, name=node) else: for updateid in updatesbytarget.get((node, tenant), {}): yield msg.ChildCollection(updateid)
def retrieve_firmware(configmanager, creds, node, results, element): if len(element) == 3: results.put(msg.ChildCollection('all')) return wc = cnos_login(node, configmanager, creds) sysinfo = wc.grab_json_response('/nos/api/sysinfo/inventory') items = [{ 'Software': { 'version': sysinfo['Software Revision'] }, }, { 'BIOS': { 'version': sysinfo['BIOS Revision'] }, }] results.put(msg.Firmware(items, node))
def _handle_neighbor_query(pathcomponents, configmanager): choices, parms, listrequested, childcoll = _parameterize_path( pathcomponents) if not childcoll: # this means it's a single entry with by-peerid # guaranteed if (parms['by-peerid'] not in _neighbypeerid and _neighbypeerid.get('!!vintage', 0) < util.monotonic_time() - 60): list(update_neighbors(configmanager)) if parms['by-peerid'] not in _neighbypeerid: raise exc.NotFoundException('No matching peer known') return _dump_neighbordatum(_neighbypeerid[parms['by-peerid']]) if not listrequested: # the query is for currently valid choices return [msg.ChildCollection(x + '/') for x in sorted(list(choices))] if listrequested not in multi_selectors | single_selectors: raise exc.NotFoundException('{0} is not found'.format(listrequested)) if 'by-switch' in parms: update_switch_data(parms['by-switch'], configmanager) else: list(update_neighbors(configmanager)) return list_info(parms, listrequested)
def handle_alerts(self): if self.element[3] == 'destinations': if len(self.element) == 4: # A list of destinations maxdest = self.ipmicmd.get_alert_destination_count() for alertidx in xrange(0, maxdest + 1): self.output.put(msg.ChildCollection(alertidx)) return elif len(self.element) == 5: alertidx = int(self.element[-1]) if self.op == 'read': destdata = self.ipmicmd.get_alert_destination(alertidx) self.output.put( msg.AlertDestination( ip=destdata['address'], acknowledge=destdata['acknowledge_required'], acknowledge_timeout=destdata.get( 'acknowledge_timeout', None), retries=destdata['retries'], name=self.node)) return elif self.op == 'update': alertparms = self.inputdata.alert_params_by_node(self.node) alertargs = {} if 'acknowledge' in alertparms: alertargs['acknowledge_required'] = alertparms[ 'acknowledge'] if 'acknowledge_timeout' in alertparms: alertargs['acknowledge_timeout'] = alertparms[ 'acknowledge_timeout'] if 'ip' in alertparms: alertargs['ip'] = alertparms['ip'] if 'retries' in alertparms: alertargs['retries'] = alertparms['retries'] self.ipmicmd.set_alert_destination(destination=alertidx, **alertargs) return elif self.op == 'delete': self.ipmicmd.clear_alert_destination(alertidx) return raise Exception('Not implemented')
def handle_nets(self): if len(self.element) == 3: if self.op != 'read': self.output.put( msg.ConfluentNodeError(self.node, 'Unsupported operation')) return self.output.put(msg.ChildCollection('management')) elif len(self.element) == 4 and self.element[-1] == 'management': if self.op == 'read': lancfg = self.ipmicmd.get_net_configuration() self.output.put(msg.NetworkConfiguration( self.node, ipv4addr=lancfg['ipv4_address'], ipv4gateway=lancfg['ipv4_gateway'], ipv4cfgmethod=lancfg['ipv4_configuration'], hwaddr=lancfg['mac_address'] )) elif self.op == 'update': config = self.inputdata.netconfig(self.node) self.ipmicmd.set_net_configuration( ipv4_address=config['ipv4_address'], ipv4_configuration=config['ipv4_configuration'], ipv4_gateway=config['ipv4_gateway'])
def list_importing(): return [msg.ChildCollection(x) for x in importing]
def handle_read_api_request(pathcomponents): # TODO(jjohnson2): discovery core.py api handler design, apply it here # to make this a less tangled mess as it gets extended if len(pathcomponents) == 1: return [msg.ChildCollection('macs/')] elif len(pathcomponents) == 2: return [ msg.ChildCollection(x) for x in ( # 'by-node/', 'by-mac/', 'by-switch/', 'rescan') ] if False and pathcomponents[2] == 'by-node': # TODO: should be list of node names, and then under that 'by-mac' if len(pathcomponents) == 3: return [ msg.ChildCollection(x.replace(':', '-')) for x in sorted(list(_nodesbymac)) ] elif len(pathcomponents) == 4: macaddr = pathcomponents[-1].replace('-', ':') return dump_macinfo(macaddr) elif pathcomponents[2] == 'by-mac': if len(pathcomponents) == 3: return [ msg.ChildCollection(x.replace(':', '-')) for x in sorted(list(_macmap)) ] elif len(pathcomponents) == 4: return dump_macinfo(pathcomponents[-1]) elif pathcomponents[2] == 'by-switch': if len(pathcomponents) == 3: return [ msg.ChildCollection(x + '/') for x in sorted(list(_macsbyswitch)) ] if len(pathcomponents) == 4: return [msg.ChildCollection('by-port/')] if len(pathcomponents) == 5: switchname = pathcomponents[-2] if switchname not in _macsbyswitch: raise exc.NotFoundException( 'No known macs for switch {0}'.format(switchname)) return [ msg.ChildCollection(x.replace('/', '-') + '/') for x in sorted(list(_macsbyswitch[switchname])) ] if len(pathcomponents) == 6: return [msg.ChildCollection('by-mac/')] if len(pathcomponents) == 7: switchname = pathcomponents[-4] portname = pathcomponents[-2] try: if portname not in _macsbyswitch[switchname]: portname = portname.replace('-', '/') maclist = _macsbyswitch[switchname][portname] except KeyError: raise exc.NotFoundException('No known macs for switch {0} ' 'port {1}'.format( switchname, portname)) return [ msg.ChildCollection(x.replace(':', '-')) for x in sorted(maclist) ] if len(pathcomponents) == 8: return dump_macinfo(pathcomponents[-1]) raise exc.NotFoundException('Unrecognized path {0}'.format( '/'.join(pathcomponents)))
def enumerate_collections(collections): for collection in collections: yield msg.ChildCollection(collection)
def iterate_collections(iterable, forcecollection=True): for coll in iterable: if forcecollection and coll[-1] != '/': coll += '/' yield msg.ChildCollection(coll, candelete=True)
def handle_path(path, operation, configmanager, inputdata=None, autostrip=True): """Given a full path request, return an object. The plugins should generally return some sort of iterator. An exception is made for console/session, which should return a class with connect(), read(), write(bytes), and close() """ pathcomponents = path.split('/') del pathcomponents[0] # discard the value from leading / if pathcomponents[-1] == '': del pathcomponents[-1] if not pathcomponents: # root collection list return enumerate_collections(rootcollections) elif pathcomponents[0] == 'noderange': return handle_node_request(configmanager, inputdata, operation, pathcomponents, autostrip) elif pathcomponents[0] == 'deployment': return handle_deployment(configmanager, inputdata, pathcomponents, operation) elif pathcomponents[0] == 'nodegroups': return handle_nodegroup_request(configmanager, inputdata, pathcomponents, operation) elif pathcomponents[0] == 'nodes': # single node request of some sort return handle_node_request(configmanager, inputdata, operation, pathcomponents, autostrip) elif pathcomponents[0] == 'discovery': return disco.handle_api_request(configmanager, inputdata, operation, pathcomponents) elif pathcomponents[0] == 'networking': return macmap.handle_api_request(configmanager, inputdata, operation, pathcomponents) elif pathcomponents[0] == 'version': return (msg.Attributes(kv={'version': confluent.__version__}), ) elif pathcomponents[0] == 'usergroups': # TODO: when non-administrator accounts exist, # they must only be allowed to see their own user try: usergroup = pathcomponents[1] except IndexError: # it's just users/ if operation == 'create': inputdata = msg.get_input_message(pathcomponents, operation, inputdata, configmanager=configmanager) create_usergroup(inputdata.attribs, configmanager) return iterate_collections(configmanager.list_usergroups(), forcecollection=False) if usergroup not in configmanager.list_usergroups(): raise exc.NotFoundException("Invalid usergroup %s" % usergroup) if operation == 'retrieve': return show_usergroup(usergroup, configmanager) elif operation == 'delete': return delete_usergroup(usergroup, configmanager) elif operation == 'update': inputdata = msg.get_input_message(pathcomponents, operation, inputdata, configmanager=configmanager) update_usergroup(usergroup, inputdata.attribs, configmanager) return show_usergroup(usergroup, configmanager) elif pathcomponents[0] == 'users': # TODO: when non-administrator accounts exist, # they must only be allowed to see their own user try: user = pathcomponents[1] except IndexError: # it's just users/ if operation == 'create': inputdata = msg.get_input_message(pathcomponents, operation, inputdata, configmanager=configmanager) create_user(inputdata.attribs, configmanager) return iterate_collections(configmanager.list_users(), forcecollection=False) if user not in configmanager.list_users(): raise exc.NotFoundException("Invalid user %s" % user) if operation == 'retrieve': return show_user(user, configmanager) elif operation == 'delete': return delete_user(user, configmanager) elif operation == 'update': inputdata = msg.get_input_message(pathcomponents, operation, inputdata, configmanager=configmanager) update_user(user, inputdata.attribs, configmanager) return show_user(user, configmanager) elif pathcomponents[0] == 'events': try: element = pathcomponents[1] except IndexError: if operation != 'retrieve': raise exc.InvalidArgumentException('Target is read-only') return (msg.ChildCollection('decode'), ) if element != 'decode': raise exc.NotFoundException() if operation == 'update': return alerts.decode_alert(inputdata, configmanager) elif pathcomponents[0] == 'discovery': return handle_discovery(pathcomponents[1:], operation, configmanager, inputdata) else: raise exc.NotFoundException()
def list_sensors(configmanager, creds, node, results, element): wc = WebClient(node, configmanager, creds) sensors = wc.fetch('/affluent/sensors/hardware/all', results) for sensor in sensors['item']: results.put(msg.ChildCollection(sensor))
def handle_read_api_request(pathcomponents, configmanager): # TODO(jjohnson2): discovery core.py api handler design, apply it here # to make this a less tangled mess as it gets extended if len(pathcomponents) == 1: return [ msg.ChildCollection('macs/'), msg.ChildCollection('neighbors/') ] elif pathcomponents[1] == 'neighbors': if len(pathcomponents) == 3 and pathcomponents[-1] == 'by-switch': return [ msg.ChildCollection(x + '/') for x in list_switches(configmanager) ] else: return _handle_neighbor_query(pathcomponents[2:], configmanager) elif len(pathcomponents) == 2: if pathcomponents[-1] == 'macs': return [ msg.ChildCollection(x) for x in ( # 'by-node/', 'by-mac/', 'by-switch/', 'rescan') ] elif pathcomponents[-1] == 'neighbors': return [msg.ChildCollection('by-switch/')] else: raise exc.NotFoundException( 'Unknown networking resource {0}'.format(pathcomponents[-1])) if False and pathcomponents[2] == 'by-node': # TODO: should be list of node names, and then under that 'by-mac' if len(pathcomponents) == 3: return [ msg.ChildCollection(x.replace(':', '-')) for x in util.natural_sort(list(_nodesbymac)) ] elif len(pathcomponents) == 4: macaddr = pathcomponents[-1].replace('-', ':') return dump_macinfo(macaddr) elif pathcomponents[2] == 'by-mac': if len(pathcomponents) == 3: return [ msg.ChildCollection(x.replace(':', '-')) for x in sorted(list(_apimacmap)) ] elif len(pathcomponents) == 4: return dump_macinfo(pathcomponents[-1]) elif pathcomponents[2] == 'by-switch': if len(pathcomponents) == 3: return [ msg.ChildCollection(x + '/') for x in list_switches(configmanager) ] if len(pathcomponents) == 4: return [msg.ChildCollection('by-port/')] if len(pathcomponents) == 5: switchname = pathcomponents[-2] if switchname not in _macsbyswitch: raise exc.NotFoundException( 'No known macs for switch {0}'.format(switchname)) return [ msg.ChildCollection(x.replace('/', '-') + '/') for x in util.natural_sort(list(_macsbyswitch[switchname])) ] if len(pathcomponents) == 6: return [msg.ChildCollection('by-mac/')] if len(pathcomponents) == 7: switchname = pathcomponents[-4] portname = pathcomponents[-2] try: if portname not in _macsbyswitch[switchname]: portname = portname.replace('-', '/') maclist = _macsbyswitch[switchname][portname] except KeyError: foundsomemacs = False if switchname in _macsbyswitch: try: matcher = re.compile(portname) except Exception: raise exc.InvalidArgumentException( 'Invalid regular expression specified') maclist = [] for actualport in _macsbyswitch[switchname]: if bool(matcher.match(actualport)): foundsomemacs = True maclist = maclist + _macsbyswitch[switchname][ actualport] if not foundsomemacs: raise exc.NotFoundException('No known macs for switch {0} ' 'port {1}'.format( switchname, portname)) return [ msg.ChildCollection(x.replace(':', '-')) for x in sorted(maclist) ] if len(pathcomponents) == 8: return dump_macinfo(pathcomponents[-1]) elif pathcomponents[2] == 'rescan': return [msg.KeyValueData({'scanning': mapupdating.locked()})] raise exc.NotFoundException('Unrecognized path {0}'.format( '/'.join(pathcomponents)))
def list_leds(self): self.output.put(msg.ChildCollection('all')) for category, info in self.ipmicmd.get_leds(): self.output.put(msg.ChildCollection(simplify_name(category)))