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]