From ca2a4ed77508e2fa879b54ae51d039f11ada0cdc Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 15 Apr 2014 14:59:36 -0400 Subject: [PATCH] Add health/hardware resource to nodes and wire up for IPMI --- TODO | 4 ++ confluent/messages.py | 92 +++++++++++++++++++++++++----- confluent/pluginapi.py | 6 ++ plugins/hardwaremanagement/ipmi.py | 51 +++++++++++++++-- 4 files changed, 134 insertions(+), 19 deletions(-) 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 = "' + + + 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 += ('' ).format(type, key, self.desc) for v in val: - snippet += ('' - ).format(type, key, v, self.desc) - snippet += ( - '' - '').format(type, key, self.desc) + if self.readonly: + snippet += _htmlify_structure(v) + else: + snippet += ('' + ).format(type, key, v, self.desc) + if not self.readonly: + snippet += ( + '' + '').format(type, key, self.desc) return snippet - snippet += (key + ":" + - '' - ).format(type, key, value, self.desc) + if self.readonly: + snippet += "{0}: {1}".format(key, value) + else: + snippet += (key + ":" + + '' + ).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) -