diff --git a/TODO b/TODO
index cc774508..2c246bc0 100644
--- a/TODO
+++ b/TODO
@@ -36,3 +36,7 @@ Traceback (most recent call last):
     passvalue = pluginmap[plugpath].__dict__[operation](
 KeyError: ''
 
+
+-reuse SDR to make health faster
+-have pyghmi and friends do multiprocessing pools (particularly the PBKDF stuff in auth)
+-bad password retrie
diff --git a/confluent/messages.py b/confluent/messages.py
index 1ded84b6..c6eaa25d 100644
--- a/confluent/messages.py
+++ b/confluent/messages.py
@@ -21,7 +21,27 @@ import confluent.exceptions as exc
 import json
 
 
+def _htmlify_structure(indict):
+    ret = "<ul>"
+    if isinstance(indict, dict):
+        for key in indict.iterkeys():
+            ret += "<li>{0}: ".format(key)
+            if type(indict[key]) in (str, unicode):
+                ret += indict[key]
+            else:
+                ret += _htmlify_structure(indict[key])
+    elif isinstance(indict, list):
+        if type(indict[0]) in (str, unicode):
+            ret += ",".join(indict)
+        else:
+            for v in indict:
+                ret += _htmlify_structure(v)
+    return ret + '</ul>'
+
+
+
 class ConfluentMessage(object):
+    readonly = False
     defaultvalue = ''
     defaulttype = 'text'
 
@@ -72,24 +92,31 @@ class ConfluentMessage(object):
                 value = '********'
             if isinstance(val, list):
                 snippet += key + ":"
-                if len(val) == 0:
+                if len(val) == 0 and not self.readonly:
                     snippet += ('<input type="{0}" name="{1}" value="" '
                                 ' "title="{2}">'
                                 ).format(type, key, self.desc)
                 for v in val:
-                    snippet += ('<input type="{0}" name="{1}" value="{2}" '
-                                ' "title="{3}">'
-                                ).format(type, key, v, self.desc)
-                snippet += (
-                    '<input type="{0}" name="{1}" value="" title="{2}">'
-                    '<input type="checkbox" name="restexplorerhonorkey" '
-                    'value="{1}">').format(type, key, self.desc)
+                    if self.readonly:
+                        snippet += _htmlify_structure(v)
+                    else:
+                        snippet += ('<input type="{0}" name="{1}" value="{2}" '
+                                    ' "title="{3}">'
+                                    ).format(type, 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(type, key, self.desc)
                 return snippet
-            snippet += (key + ":" +
-                        '<input type="{0}" name="{1}" value="{2}" '
-                        'title="{3}"><input type="checkbox" '
-                        'name="restexplorerhonorkey" value="{1}">'
-                        ).format(type, key, value, self.desc)
+            if self.readonly:
+                snippet += "{0}: {1}".format(key, value)
+            else:
+                snippet += (key + ":" +
+                            '<input type="{0}" name="{1}" value="{2}" '
+                            'title="{3}"><input type="checkbox" '
+                            'name="restexplorerhonorkey" value="{1}">'
+                            ).format(type, key, value, self.desc)
             if len(notes) > 0:
                 snippet += '(' + ','.join(notes) + ')'
         return snippet
@@ -325,6 +352,45 @@ class PowerState(ConfluentChoiceMessage):
         }
 
 
+class SensorReadings(ConfluentMessage):
+    readonly = True
+
+    def __init__(self, sensors=[], name=None):
+        readings = []
+        for sensor in sensors:
+            sensordict = {'name': sensor['name']}
+            if 'value' in sensor:
+                sensordict['value'] = sensor['value']
+            if 'units' in sensor:
+                sensordict['units'] = sensor['units']
+            if 'states' in sensor:
+                sensordict['states'] = sensor['states']
+            if 'health' in sensor:
+                sensordict['health'] = sensor['health']
+            readings.append(sensordict)
+        if name is None:
+            self.kvpairs = {'sensors': readings}
+        else:
+            self.kvpairs = {name: {'sensors': readings}}
+
+
+class HealthSummary(ConfluentMessage):
+    readonly = True
+    valid_values = set([
+        'ok',
+        'warning',
+        'critical',
+        'failed',
+    ])
+
+    def __init__(self, health, name=None):
+        if health not in self.valid_values:
+            raise ValueError("%d is not a valid health state" % health)
+        if name is None:
+            self.kvpairs = {'health': {'value': health}}
+        else:
+            self.kvpairs = {name: {'health': {'value': health}}}
+
 class Attributes(ConfluentMessage):
     def __init__(self, name=None, kv=None, desc=''):
         self.desc = desc
diff --git a/confluent/pluginapi.py b/confluent/pluginapi.py
index 19ad6bec..49db747c 100644
--- a/confluent/pluginapi.py
+++ b/confluent/pluginapi.py
@@ -98,6 +98,12 @@ noderesources = {
             'default': 'ipmi',
         }),
     },
+    'health': {
+        'hardware': PluginRoute({
+            'pluginattrs': ['hardwaremanagement.method'],
+            'default': 'ipmi',
+        }),
+    },
     'boot': {
         'nextdevice': PluginRoute({
             'pluginattrs': ['hardwaremanagement.method'],
diff --git a/plugins/hardwaremanagement/ipmi.py b/plugins/hardwaremanagement/ipmi.py
index 5dad6416..505e4e63 100644
--- a/plugins/hardwaremanagement/ipmi.py
+++ b/plugins/hardwaremanagement/ipmi.py
@@ -12,7 +12,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import collections
 import confluent.exceptions as exc
 import confluent.interface.console as conapi
 import confluent.messages as msg
@@ -21,7 +20,7 @@ import eventlet.event
 import eventlet.green.threading as threading
 import eventlet.greenpool as greenpool
 import eventlet.queue
-import os
+import pyghmi.constants as pygconstants
 import pyghmi.exceptions as pygexc
 import pyghmi.ipmi.console as console
 import pyghmi.ipmi.command as ipmicommand
@@ -156,6 +155,7 @@ class IpmiConsole(conapi.Console):
 
 class IpmiIterator(object):
     def __init__(self, operator, nodes, element, cfg, inputdata):
+        self.currdata = None
         crypt = cfg.decrypt
         cfg.decrypt = True
         configdata = cfg.get_node_attributes(nodes, _configattributes)
@@ -168,10 +168,15 @@ class IpmiIterator(object):
         return self
 
     def next(self):
-        ndata = self.gpile.next()
+        if self.currdata is None:
+            self.currdata = self.gpile.next()
         # need to apply any translations between pyghmi and confluent
-        return ndata
-
+        try:
+            retdata = self.currdata.next()
+        except AttributeError:
+            retdata = self.currdata
+            self.currdata = None
+        return retdata
 
 def perform_request(operator, node, element, configdata, inputdata):
     return IpmiHandler(operator, node, element, configdata, inputdata).handle_request()
@@ -220,6 +225,41 @@ class IpmiHandler(object):
             return self.power()
         elif self.element == [ 'boot', 'nextdevice' ]:
             return self.bootdevice()
+        elif self.element == [ 'health', 'hardware' ]:
+            return self.health()
+
+    def _str_health(self, health):
+        if pygconstants.Health.Failed & health:
+            health = 'failed'
+        elif pygconstants.Health.Critical & health:
+            health = 'critical'
+        elif pygconstants.Health.Warning & health:
+            health = 'warning'
+        else:
+            health = 'ok'
+        return health
+
+    def _dict_sensor(self, pygreading):
+        retdict = {}
+        retdict['name'] = pygreading.name
+        retdict['value'] = pygreading.value
+        retdict['states'] = pygreading.states
+        retdict['health'] = self._str_health(pygreading.health)
+        return retdict
+
+    def health(self):
+        if 'read' == self.op:
+            response = self.ipmicmd.get_health()
+            health = response['health']
+            health = self._str_health(health)
+            yield msg.HealthSummary(health, self.node)
+            if 'badreadings' in response:
+                badsensors = []
+                for reading in response['badreadings']:
+                    badsensors.append(self._dict_sensor(reading))
+                yield msg.SensorReadings(badsensors, name=self.node)
+        else:
+            raise exc.InvalidArgumentException('health is read-only')
 
     def bootdevice(self):
         if 'read' == self.op:
@@ -275,4 +315,3 @@ def update(nodes, element, configmanager, inputdata):
 def retrieve(nodes, element, configmanager, inputdata):
     initthread()
     return IpmiIterator('read', nodes, element, configmanager, inputdata)
-