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:
parent
b97cd79c3a
commit
6204628f43
@ -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:]
|
||||
|
@ -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]
|
||||
|
@ -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(
|
||||
|
@ -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([
|
||||
|
@ -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]
|
||||
|
Loading…
x
Reference in New Issue
Block a user