diff --git a/confluent_client/bin/confetty b/confluent_client/bin/confetty index b4d2525b..2e65054c 100755 --- a/confluent_client/bin/confetty +++ b/confluent_client/bin/confetty @@ -248,6 +248,9 @@ def print_result(res): attrstr = '%s=""' % key elif type(res[key]) == list: attrstr = '%s=%s' % (key, recurse_format(res[key])) + elif not isinstance(res[key], dict): + print '{0}: {1}'.format(key, res[key]) + continue elif 'value' in res[key] and res[key]['value'] is not None: attrstr = '%s="%s"' % (key, res[key]['value']) elif 'value' in res[key] and res[key]['value'] is None: @@ -258,7 +261,10 @@ def print_result(res): attrstr = '%s=""' % key else: sys.stdout.write('{0}: '.format(key)) - print_result(res[key]) + if isinstance(res[key], str) or isinstance(res[key], unicode): + print res[key] + else: + print_result(res[key]) continue if res[key] is not None and 'inheritedfrom' in res[key]: notes.append( @@ -355,9 +361,11 @@ def do_command(command, server): else: sys.stderr.write("%s: command not found...\n" % argv[0]) + def shutdown(): tlvdata.send(session.connection, {'operation': 'shutdown', 'path': '/'}) + def createresource(args): resname = args[0] attribs = args[1:] diff --git a/confluent_server/confluent/core.py b/confluent_server/confluent/core.py index b97ac7a4..7efa35c9 100644 --- a/confluent_server/confluent/core.py +++ b/confluent_server/confluent/core.py @@ -45,6 +45,7 @@ import sys pluginmap = {} + def seek_element(currplace, currkey): try: return currplace[currkey] @@ -55,10 +56,11 @@ def seek_element(currplace, currkey): return currplace raise + def nested_lookup(nestdict, key): try: return reduce(seek_element, key, nestdict) - except TypeError as e: + except TypeError: raise exc.NotFoundException("Invalid element requested") @@ -72,7 +74,7 @@ def load_plugins(): if not os.path.isdir(plugindir): continue sys.path.append(plugindir) - #two passes, to avoid adding both py and pyc files + # two passes, to avoid adding both py and pyc files for plugin in os.listdir(plugindir): if plugin.startswith('.'): continue @@ -98,6 +100,7 @@ class PluginRoute(object): def __init__(self, routedict): self.routeinfo = routedict + class PluginCollection(object): def __init__(self, routedict): self.routeinfo = routedict @@ -111,7 +114,7 @@ noderesources = { }), }, 'console': { - #this is a dummy value, http or socket must handle special + # this is a dummy value, http or socket must handle special 'session': PluginRoute({}), }, 'power': { @@ -140,6 +143,14 @@ noderesources = { 'all': PluginRoute({'handler': 'attributes'}), 'current': PluginRoute({'handler': 'attributes'}), }, + 'inventory': { + 'hardware': { + 'all': PluginCollection({ + 'pluginattrs': ['hardwaremanagement.method'], + 'default': 'ipmi', + }), + }, + }, 'sensors': { 'hardware': { 'all': PluginCollection({ @@ -313,6 +324,7 @@ def enumerate_collections(collections): def handle_nodegroup_request(configmanager, inputdata, pathcomponents, operation): iscollection = False + routespec = None if len(pathcomponents) < 2: if operation == "create": inputdata = msg.InputAttributes(pathcomponents, inputdata) @@ -337,10 +349,10 @@ def handle_nodegroup_request(configmanager, inputdata, if iscollection: if operation == "delete": return delete_nodegroup_collection(pathcomponents, - configmanager) + configmanager) elif operation == "retrieve": return enumerate_nodegroup_collection(pathcomponents, - configmanager) + configmanager) else: raise Exception("TODO") plugroute = routespec.routeinfo @@ -430,7 +442,7 @@ def handle_node_request(configmanager, inputdata, operation, pathcomponents, operation, inputdata, nodes) if 'handler' in plugroute: # fixed handler definition, easy enough hfunc = getattr(pluginmap[plugroute['handler']], operation) - passvalue =hfunc( + passvalue = hfunc( nodes=nodes, element=pathcomponents, configmanager=configmanager, inputdata=inputdata) @@ -467,6 +479,7 @@ def handle_node_request(configmanager, inputdata, operation, else: return stripnode(passvalues[0], nodes[0]) + def handle_path(path, operation, configmanager, inputdata=None): """Given a full path request, return an object. @@ -482,17 +495,17 @@ def handle_path(path, operation, configmanager, inputdata=None): return enumerate_collections(rootcollections) elif pathcomponents[0] == 'noderange': return handle_node_request(configmanager, inputdata, operation, - pathcomponents) + pathcomponents) elif pathcomponents[0] == 'nodegroups': return handle_nodegroup_request(configmanager, inputdata, - pathcomponents, - operation) + pathcomponents, + operation) elif pathcomponents[0] == 'nodes': - #single node request of some sort + # single node request of some sort return handle_node_request(configmanager, inputdata, - operation, pathcomponents) + operation, pathcomponents) elif pathcomponents[0] == 'users': - #TODO: when non-administrator accounts exist, + # TODO: when non-administrator accounts exist, # they must only be allowed to see their own user try: user = pathcomponents[1] diff --git a/confluent_server/confluent/httpapi.py b/confluent_server/confluent/httpapi.py index 650dcbf3..075dd315 100644 --- a/confluent_server/confluent/httpapi.py +++ b/confluent_server/confluent/httpapi.py @@ -429,7 +429,6 @@ def _assemble_html(responses, resource, querydict, url, extension): iscollection = True yield '../{0}
'.format( extension) - else: iscollection = False yield './{0}
'.format( diff --git a/confluent_server/confluent/messages.py b/confluent_server/confluent/messages.py index 5a597c3d..03175cc3 100644 --- a/confluent_server/confluent/messages.py +++ b/confluent_server/confluent/messages.py @@ -33,8 +33,14 @@ def _htmlify_structure(indict): ret += _htmlify_structure(indict[key]) elif isinstance(indict, list): if len(indict) > 0: - if type(indict[0]) in (str, unicode): - ret += ",".join(indict) + if type(indict[0]) in (str, unicode, None): + nd = [] + for datum in indict: + if datum is None: + nd.append('') + else: + nd.append(datum) + ret += ",".join(nd) else: for v in indict: ret += _htmlify_structure(v) @@ -75,7 +81,7 @@ class ConfluentMessage(object): self.kvpairs = self.kvpairs[node] def html(self, extension=''): - #this is used to facilitate the api explorer feature + # this is used to facilitate the api explorer feature if not hasattr(self, 'stripped'): self.stripped = False if not hasattr(self, 'notnode'): @@ -96,6 +102,27 @@ class ConfluentMessage(object): value = self.defaultvalue valtype = self.defaulttype notes = [] + + if isinstance(val, list): + snippet += key + ":" + if len(val) == 0 and not self.readonly: + snippet += ('' + ).format(valtype, key, self.desc) + for v in val: + if self.readonly: + snippet += _htmlify_structure(v) + else: + snippet += ('' + ).format(valtype, key, v, self.desc) + if not self.readonly: + snippet += ( + '' + '').format(valtype, key, self.desc) + return snippet + snippet += repr(val) if val is not None and 'value' in val: value = val['value'] if 'inheritedfrom' in val: @@ -119,25 +146,6 @@ class ConfluentMessage(object): if 'inheritedfrom' in val: notes.append('Inherited from %s' % val['inheritedfrom']) value = '********' - if isinstance(val, list): - snippet += key + ":" - if len(val) == 0 and not self.readonly: - snippet += ('' - ).format(valtype, key, self.desc) - for v in val: - if self.readonly: - snippet += _htmlify_structure(v) - else: - snippet += ('' - ).format(valtype, key, v, self.desc) - if not self.readonly: - snippet += ( - '' - '').format(valtype, key, self.desc) - return snippet if self.readonly: snippet += "{0}: {1}".format(key, value) else: @@ -163,8 +171,8 @@ class ConfluentNodeError(object): return self.node + ":" + self.error def strip_node(self, node): - #NOTE(jbjohnso): For single node errors, raise exception to - #trigger what a developer of that medium would expect + # NOTE(jjohnson2): For single node errors, raise exception to + # trigger what a developer of that medium would expect raise Exception(self.error) @@ -185,6 +193,7 @@ class ConfluentTargetNotFound(ConfluentNodeError): def strip_node(self, node): raise exc.NotFoundException(self.error) + class ConfluentTargetInvalidCredentials(ConfluentNodeError): def __init__(self, node): self.node = node @@ -248,7 +257,6 @@ class LinkRelation(ConfluentMessage): self.href = '' self.rel = '' - def json(self): """Provide json_hal style representation of the relation. @@ -372,7 +380,7 @@ class ConfluentInputMessage(ConfluentMessage): if not inputdata: raise exc.InvalidArgumentException('missing input data') if self.keyname not in inputdata: - #assume we have nested information + # assume we have nested information for key in nodes: if key not in inputdata: raise exc.InvalidArgumentException(key + ' not in request') @@ -388,7 +396,8 @@ class ConfluentInputMessage(ConfluentMessage): else: # we have a state argument not by node datum = inputdata if self.keyname not in datum: - raise exc.InvalidArgumentException('missing {0} argument'.format(self.keyname)) + raise exc.InvalidArgumentException( + 'missing {0} argument'.format(self.keyname)) elif datum[self.keyname] not in self.valid_values: raise exc.InvalidArgumentException(datum[self.keyname] + ' is not one of ' + @@ -448,7 +457,7 @@ class BootDevice(ConfluentChoiceMessage): self.kvpairs = { node: { 'nextdevice': {'value': device}, - 'bootmode': {'value': bootmode }, + 'bootmode': {'value': bootmode}, } } @@ -518,8 +527,6 @@ class PowerState(ConfluentChoiceMessage): keyname = 'state' - - class SensorReadings(ConfluentMessage): readonly = True @@ -543,6 +550,16 @@ class SensorReadings(ConfluentMessage): self.kvpairs = {name: {'sensors': readings}} +class KeyValueData(ConfluentMessage): + readonly = True + + def __init__(self, kvdata, name=None): + self.notnode = name is None + if self.notnode: + self.kvpairs = kvdata + else: + self.kvpairs = {name: kvdata} + class HealthSummary(ConfluentMessage): readonly = True valid_values = set([ diff --git a/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py b/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py index bc51c86c..f22eb098 100644 --- a/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py +++ b/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py @@ -53,6 +53,34 @@ def simplify_name(name): return name.lower().replace(' ', '_') +def sanitize_invdata(indata): + """Sanitize pyghmi data + + pyghmi will return bytearrays when it has no idea what to do. In our + case, we will change those to hex strings. Additionally, ignore 'extra' + fields if the oem_parser is set + """ + if 'oem_parser' in indata and indata['oem_parser'] is not None: + if 'board_extra' in indata: + del indata['board_extra'] + if 'chassis_extra' in indata: + del indata['chassis_extra'] + if 'product_extra' in indata: + del indata['product_extra'] + for k in indata: + if isinstance(indata[k], bytearray): + indata[k] = '0x' + ''.join(format(x, '02x') for x in indata[k]) + elif isinstance(indata[k], dict): + sanitize_invdata(indata[k]) + elif isinstance(indata[k], list): + for idx, value in enumerate(indata[k]): + if isinstance(value, bytearray): + indata[k][idx] = '0x' + ''.join( + format(x, '02x') for x in indata[k][idx]) + + + + class IpmiCommandWrapper(ipmicommand.Command): def __init__(self, node, cfm, **kwargs): self._attribwatcher = cfm.watch_attributes( @@ -233,7 +261,8 @@ def perform_request(operator, node, element, except exc.TargetEndpointUnreachable as tu: results.put(msg.ConfluentTargetTimeout(node, str(tu))) except Exception as e: - results.put(msg.ConfluentNodeError(node, str(e))) + results.put(msg.ConfluentNodeError( + node, 'IPMI PluginException: ' + str(e))) finally: results.put('Done') @@ -250,6 +279,7 @@ def _dict_sensor(pygreading): class IpmiHandler(object): def __init__(self, operation, node, element, cfd, inputdata, cfg, output): self.sensormap = {} + self.invmap = {} self.output = output self.sensorcategory = None self.broken = False @@ -319,6 +349,13 @@ class IpmiHandler(object): self.identify() elif self.element[0] == 'sensors': self.handle_sensors() + elif self.element[0] == 'inventory': + self.handle_inventory() + + def make_inventory_map(self): + invnames = self.ipmicmd.get_inventory_descriptions() + for name in invnames: + self.invmap[simplify_name(name)] = name def make_sensor_map(self, sensors=None): if sensors is None: @@ -357,6 +394,35 @@ class IpmiHandler(object): except pygexc.IpmiException: self.output.put(msg.ConfluentTargetTimeout(self.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_inventory(self): + if len(self.element) == 3: # list things in inventory + return self.list_inventory() + elif len(self.element) == 4: # actually read inventory data + return self.read_inventory(self.element[-1]) + raise Exception('Unsupported scenario...') + + def read_inventory(self, component): + if component == 'all': + for invdata in self.ipmicmd.get_inventory(): + if invdata[1] is None: + newinf = {'present': False, 'information': None} + else: + newinf = {'present': True, 'information': invdata[1]} + newinf['name'] = invdata[0] + newinvdata = {'inventory': [newinf]} + sanitize_invdata(newinvdata['inventory'][0]) + self.output.put(msg.KeyValueData(newinvdata, self.node)) + def handle_sensors(self): if self.element[-1] == '': self.element = self.element[:-1]