2
0
mirror of https://github.com/xcat2/confluent.git synced 2025-02-27 07:41:13 +00:00

Add support for inventory

Present 'inventory/hardware/all/' hierarchy.  Currently
only ipmi and 'all/all' works.  The data structure may be amended in
the very near future as well.
This commit is contained in:
Jarrod Johnson 2015-04-27 16:57:52 -04:00
parent b97cd79c3a
commit 6204628f43
5 changed files with 148 additions and 45 deletions

View File

@ -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:]

View File

@ -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]

View File

@ -429,7 +429,6 @@ def _assemble_html(responses, resource, querydict, url, extension):
iscollection = True
yield '<a rel="collection" href="../{0}">../{0}</a><br>'.format(
extension)
else:
iscollection = False
yield '<a rel="collection" href="./{0}">./{0}</a><br>'.format(

View File

@ -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 += ('<input type="{0}" name="{1}" value="" '
' "title="{2}">'
).format(valtype, key, self.desc)
for v in val:
if self.readonly:
snippet += _htmlify_structure(v)
else:
snippet += ('<input type="{0}" name="{1}" value="{2}" '
' "title="{3}">'
).format(valtype, key, v, self.desc)
if not self.readonly:
snippet += (
'<input type="{0}" name="{1}" value="" title="{2}">'
'<input type="checkbox" name="restexplorerhonorkey" '
'value="{1}">').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 += ('<input type="{0}" name="{1}" value="" '
' "title="{2}">'
).format(valtype, key, self.desc)
for v in val:
if self.readonly:
snippet += _htmlify_structure(v)
else:
snippet += ('<input type="{0}" name="{1}" value="{2}" '
' "title="{3}">'
).format(valtype, key, v, self.desc)
if not self.readonly:
snippet += (
'<input type="{0}" name="{1}" value="" title="{2}">'
'<input type="checkbox" name="restexplorerhonorkey" '
'value="{1}">').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([

View File

@ -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]