From 41e927622ec7d2b028de0a95a12a385477fe900a Mon Sep 17 00:00:00 2001 From: Chris Dent Date: Tue, 8 Jul 2014 16:06:49 +0100 Subject: [PATCH] Implement consuming ipmi notifications from Ironic This borrows heavily from the compute notifications code. The basic idea is that there are few classes of samples that can be had (Fan, Temperature, Voltage, Current). Within these are potentially multiple individual sensors and values. In all cases the volume and unit of a sample can always be extracted from the value of a 'Sensor Reading' key. The volume is cast to a float. If the value is 'Disabled' the data is dropped. Each subclass exists to allow flexible handling via stevedore, but all activity is performed in the superclass. The supplied good test data is the currently blessed sample from the Ironic team. The bad test data tickles either skipping missing data or producing warnings in the log. Identifier and name construction is worth a close look because this is an area of frequent discussion: * resource_id is composed from node_uuid and sensor id * meter name is based on the class name and is generic, only paired with a resource_id does it identify a specific sensor * unit is parsed out of sensor data and mapped to SI units Change-Id: Ie7ac92aebb45fc23d6134b4d6693f8a1e4d4ea40 Implements blueprint ironic-notifications --- ceilometer/hardware/notifications/__init__.py | 0 ceilometer/hardware/notifications/ipmi.py | 176 ++++ .../tests/hardware/notifications/__init__.py | 0 .../hardware/notifications/ipmi_test_data.py | 785 ++++++++++++++++++ .../tests/hardware/notifications/test_ipmi.py | 213 +++++ doc/source/measurements.rst | 17 + setup.cfg | 4 + 7 files changed, 1195 insertions(+) create mode 100644 ceilometer/hardware/notifications/__init__.py create mode 100644 ceilometer/hardware/notifications/ipmi.py create mode 100644 ceilometer/tests/hardware/notifications/__init__.py create mode 100644 ceilometer/tests/hardware/notifications/ipmi_test_data.py create mode 100644 ceilometer/tests/hardware/notifications/test_ipmi.py diff --git a/ceilometer/hardware/notifications/__init__.py b/ceilometer/hardware/notifications/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ceilometer/hardware/notifications/ipmi.py b/ceilometer/hardware/notifications/ipmi.py new file mode 100644 index 00000000..35fc7520 --- /dev/null +++ b/ceilometer/hardware/notifications/ipmi.py @@ -0,0 +1,176 @@ +# +# Copyright 2014 Red Hat +# +# Author: Chris Dent +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +"""Converters for producing hardware sensor data sample messages from +notification events. +""" + +from oslo.config import cfg +from oslo import messaging + +from ceilometer.openstack.common import log +from ceilometer import plugin +from ceilometer import sample + +LOG = log.getLogger(__name__) + +OPTS = [ + cfg.StrOpt('ironic_exchange', + default='ironic', + help='Exchange name for Ironic notifications.'), +] + + +cfg.CONF.register_opts(OPTS) + + +# Map unit name to SI +UNIT_MAP = { + 'Watts': 'W', + 'Volts': 'V', +} + + +class InvalidSensorData(ValueError): + pass + + +class SensorNotification(plugin.NotificationBase): + """A generic class for extracting samples from sensor data notifications. + + A notification message can contain multiple samples from multiple + sensors, all with the same basic structure: the volume for the sample + is found as part of the value of a 'Sensor Reading' key. The unit + is in the same value. + + Subclasses exist solely to allow flexibility with stevedore configuration. + """ + + event_types = ['hardware.ipmi.*'] + metric = None + + @staticmethod + def get_targets(conf): + """oslo.messaging.TargetS for this this plugin.""" + return [messaging.Target(topic=topic, + exchange=conf.ironic_exchange) + for topic in conf.notification_topics] + + def _get_sample(self, message): + try: + return (payload for _, payload + in message['payload'][self.metric].items()) + except KeyError: + return [] + + @staticmethod + def _validate_reading(data): + """Some sensors read "Disabled".""" + return data != 'Disabled' + + @staticmethod + def _transform_id(data): + return data.lower().replace(' ', '_') + + @staticmethod + def _parse_reading(data): + try: + volume, unit = data.split(' ', 1) + unit = unit.rsplit(' ', 1)[-1] + return float(volume), UNIT_MAP.get(unit, unit) + except ValueError: + raise InvalidSensorData('unable to parse sensor reading: %s' % + data) + + def _package_payload(self, message, payload): + info = {} + info['publisher_id'] = message['publisher_id'] + info['timestamp'] = message['payload']['timestamp'] + info['event_type'] = message['payload']['event_type'] + info['user_id'] = message['payload'].get('user_id') + info['project_id'] = message['payload'].get('project_id') + # NOTE(chdent): How much of the payload should we keep? + info['payload'] = payload + return info + + def process_notification(self, message): + """Read and process a notification. + + The guts of a message are in dict value of a 'payload' key + which then itself has a payload key containing a dict of + multiple sensor readings. + + If expected keys in the payload are missing or values + are not in the expected form for transformations, + KeyError and ValueError are caught and the current + sensor payload is skipped. + """ + payloads = self._get_sample(message['payload']) + for payload in payloads: + try: + # Provide a fallback resource_id in case parts are missing. + resource_id = 'missing id' + try: + resource_id = '%(nodeid)s-%(sensorid)s' % { + 'nodeid': message['payload']['node_uuid'], + 'sensorid': self._transform_id(payload['Sensor ID']) + } + except KeyError as exc: + raise InvalidSensorData('missing key in payload: %s' % exc) + + info = self._package_payload(message, payload) + + try: + sensor_reading = info['payload']['Sensor Reading'] + except KeyError as exc: + raise InvalidSensorData( + "missing 'Sensor Reading' in payload" + ) + + if self._validate_reading(sensor_reading): + volume, unit = self._parse_reading(sensor_reading) + yield sample.Sample.from_notification( + name='hardware.ipmi.%s' % self.metric.lower(), + type=sample.TYPE_GAUGE, + unit=unit, + volume=volume, + resource_id=resource_id, + message=info, + user_id=info['user_id'], + project_id=info['project_id']) + + except InvalidSensorData as exc: + LOG.warn( + 'invalid sensor data for %(resource)s: %(error)s' % + dict(resource=resource_id, error=exc) + ) + continue + + +class TemperatureSensorNotification(SensorNotification): + metric = 'Temperature' + + +class CurrentSensorNotification(SensorNotification): + metric = 'Current' + + +class FanSensorNotification(SensorNotification): + metric = 'Fan' + + +class VoltageSensorNotification(SensorNotification): + metric = 'Voltage' diff --git a/ceilometer/tests/hardware/notifications/__init__.py b/ceilometer/tests/hardware/notifications/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ceilometer/tests/hardware/notifications/ipmi_test_data.py b/ceilometer/tests/hardware/notifications/ipmi_test_data.py new file mode 100644 index 00000000..6cd42059 --- /dev/null +++ b/ceilometer/tests/hardware/notifications/ipmi_test_data.py @@ -0,0 +1,785 @@ +# +# Copyright 2014 Red Hat, Inc +# +# Author: Chris Dent +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +"""Sample data for test_ipmi. + +This data is provided as a sample of the data expected from the ipmitool +driver in the Ironic project, which is the publisher of the notifications +being tested. +""" + + +SENSOR_DATA = { + 'message_id': 'f22188ca-c068-47ce-a3e5-0e27ffe234c6', + 'publisher_id': 'f23188ca-c068-47ce-a3e5-0e27ffe234c6', + 'payload': { + 'instance_uuid': 'f11251ax-c568-25ca-4582-0x27add644c6', + 'timestamp': '20140223134852', + 'node_uuid': 'f4982fd2-2f2b-4bb5-9aff-48aac801d1ad', + 'event_type': 'hardware.ipmi.metrics.update', + 'payload': { + 'Temperature': { + 'DIMM GH VR Temp (0x3b)': { + 'Status': 'ok', + 'Deassertions Enabled': 'unc+ ucr+ unr+', + 'Sensor Reading': '26 (+/- 0.500) degrees C', + 'Entity ID': '20.6 (Power Module)', + 'Assertions Enabled': 'unc+ ucr+ unr+', + 'Positive Hysteresis': '4.000', + 'Assertion Events': '', + 'Upper non-critical': '95.000', + 'Event Message Control': 'Per-threshold', + 'Upper non-recoverable': '105.000', + 'Normal Maximum': '112.000', + 'Maximum sensor range': 'Unspecified', + 'Sensor Type (Analog)': 'Temperature', + 'Readable Thresholds': 'unc ucr unr', + 'Negative Hysteresis': 'Unspecified', + 'Threshold Read Mask': 'unc ucr unr', + 'Upper critical': '100.000', + 'Sensor ID': 'DIMM GH VR Temp (0x3b)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '16.000' + }, + 'CPU1 VR Temp (0x36)': { + 'Status': 'ok', + 'Deassertions Enabled': 'unc+ ucr+ unr+', + 'Sensor Reading': '32 (+/- 0.500) degrees C', + 'Entity ID': '20.1 (Power Module)', + 'Assertions Enabled': 'unc+ ucr+ unr+', + 'Positive Hysteresis': '4.000', + 'Assertion Events': '', + 'Upper non-critical': '95.000', + 'Event Message Control': 'Per-threshold', + 'Upper non-recoverable': '105.000', + 'Normal Maximum': '112.000', + 'Maximum sensor range': 'Unspecified', + 'Sensor Type (Analog)': 'Temperature', + 'Readable Thresholds': 'unc ucr unr', + 'Negative Hysteresis': 'Unspecified', + 'Threshold Read Mask': 'unc ucr unr', + 'Upper critical': '100.000', + 'Sensor ID': 'CPU1 VR Temp (0x36)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '16.000' + }, + 'DIMM EF VR Temp (0x3a)': { + 'Status': 'ok', + 'Deassertions Enabled': 'unc+ ucr+ unr+', + 'Sensor Reading': '26 (+/- 0.500) degrees C', + 'Entity ID': '20.5 (Power Module)', + 'Assertions Enabled': 'unc+ ucr+ unr+', + 'Positive Hysteresis': '4.000', + 'Assertion Events': '', + 'Upper non-critical': '95.000', + 'Event Message Control': 'Per-threshold', + 'Upper non-recoverable': '105.000', + 'Normal Maximum': '112.000', + 'Maximum sensor range': 'Unspecified', + 'Sensor Type (Analog)': 'Temperature', + 'Readable Thresholds': 'unc ucr unr', + 'Negative Hysteresis': 'Unspecified', + 'Threshold Read Mask': 'unc ucr unr', + 'Upper critical': '100.000', + 'Sensor ID': 'DIMM EF VR Temp (0x3a)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '16.000' + }, + 'CPU2 VR Temp (0x37)': { + 'Status': 'ok', + 'Deassertions Enabled': 'unc+ ucr+ unr+', + 'Sensor Reading': '31 (+/- 0.500) degrees C', + 'Entity ID': '20.2 (Power Module)', + 'Assertions Enabled': 'unc+ ucr+ unr+', + 'Positive Hysteresis': '4.000', + 'Assertion Events': '', + 'Upper non-critical': '95.000', + 'Event Message Control': 'Per-threshold', + 'Upper non-recoverable': '105.000', + 'Normal Maximum': '112.000', + 'Maximum sensor range': 'Unspecified', + 'Sensor Type (Analog)': 'Temperature', + 'Readable Thresholds': 'unc ucr unr', + 'Negative Hysteresis': 'Unspecified', + 'Threshold Read Mask': 'unc ucr unr', + 'Upper critical': '100.000', + 'Sensor ID': 'CPU2 VR Temp (0x37)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '16.000' + }, + 'Ambient Temp (0x32)': { + 'Status': 'ok', + 'Sensor Reading': '25 (+/- 0) degrees C', + 'Entity ID': '12.1 (Front Panel Board)', + 'Assertions Enabled': 'unc+ ucr+ unr+', + 'Event Message Control': 'Per-threshold', + 'Assertion Events': '', + 'Upper non-critical': '43.000', + 'Deassertions Enabled': 'unc+ ucr+ unr+', + 'Upper non-recoverable': '50.000', + 'Positive Hysteresis': '4.000', + 'Maximum sensor range': 'Unspecified', + 'Sensor Type (Analog)': 'Temperature', + 'Readable Thresholds': 'unc ucr unr', + 'Negative Hysteresis': 'Unspecified', + 'Threshold Read Mask': 'unc ucr unr', + 'Upper critical': '46.000', + 'Sensor ID': 'Ambient Temp (0x32)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '25.000' + }, + 'Mezz Card Temp (0x35)': { + 'Status': 'Disabled', + 'Sensor Reading': 'Disabled', + 'Entity ID': '44.1 (I/O Module)', + 'Event Message Control': 'Per-threshold', + 'Upper non-critical': '70.000', + 'Upper non-recoverable': '85.000', + 'Positive Hysteresis': '4.000', + 'Maximum sensor range': 'Unspecified', + 'Sensor Type (Analog)': 'Temperature', + 'Readable Thresholds': 'unc ucr unr', + 'Negative Hysteresis': 'Unspecified', + 'Threshold Read Mask': 'unc ucr unr', + 'Upper critical': '80.000', + 'Sensor ID': 'Mezz Card Temp (0x35)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '25.000' + }, + 'PCH Temp (0x3c)': { + 'Status': 'ok', + 'Deassertions Enabled': 'unc+ ucr+ unr+', + 'Sensor Reading': '46 (+/- 0.500) degrees C', + 'Entity ID': '45.1 (Processor/IO Module)', + 'Assertions Enabled': 'unc+ ucr+ unr+', + 'Positive Hysteresis': '4.000', + 'Assertion Events': '', + 'Upper non-critical': '93.000', + 'Event Message Control': 'Per-threshold', + 'Upper non-recoverable': '103.000', + 'Normal Maximum': '112.000', + 'Maximum sensor range': 'Unspecified', + 'Sensor Type (Analog)': 'Temperature', + 'Readable Thresholds': 'unc ucr unr', + 'Negative Hysteresis': 'Unspecified', + 'Threshold Read Mask': 'unc ucr unr', + 'Upper critical': '98.000', + 'Sensor ID': 'PCH Temp (0x3c)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '16.000' + }, + 'DIMM CD VR Temp (0x39)': { + 'Status': 'ok', + 'Deassertions Enabled': 'unc+ ucr+ unr+', + 'Sensor Reading': '27 (+/- 0.500) degrees C', + 'Entity ID': '20.4 (Power Module)', + 'Assertions Enabled': 'unc+ ucr+ unr+', + 'Positive Hysteresis': '4.000', + 'Assertion Events': '', + 'Upper non-critical': '95.000', + 'Event Message Control': 'Per-threshold', + 'Upper non-recoverable': '105.000', + 'Normal Maximum': '112.000', + 'Maximum sensor range': 'Unspecified', + 'Sensor Type (Analog)': 'Temperature', + 'Readable Thresholds': 'unc ucr unr', + 'Negative Hysteresis': 'Unspecified', + 'Threshold Read Mask': 'unc ucr unr', + 'Upper critical': '100.000', + 'Sensor ID': 'DIMM CD VR Temp (0x39)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '16.000' + }, + 'PCI Riser 2 Temp (0x34)': { + 'Status': 'ok', + 'Deassertions Enabled': 'unc+ ucr+ unr+', + 'Sensor Reading': '30 (+/- 0) degrees C', + 'Entity ID': '16.2 (System Internal Expansion Board)', + 'Assertions Enabled': 'unc+ ucr+ unr+', + 'Positive Hysteresis': '4.000', + 'Assertion Events': '', + 'Upper non-critical': '70.000', + 'Event Message Control': 'Per-threshold', + 'Upper non-recoverable': '85.000', + 'Normal Maximum': '112.000', + 'Maximum sensor range': 'Unspecified', + 'Sensor Type (Analog)': 'Temperature', + 'Readable Thresholds': 'unc ucr unr', + 'Negative Hysteresis': 'Unspecified', + 'Threshold Read Mask': 'unc ucr unr', + 'Upper critical': '80.000', + 'Sensor ID': 'PCI Riser 2 Temp (0x34)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '16.000' + }, + 'DIMM AB VR Temp (0x38)': { + 'Status': 'ok', + 'Deassertions Enabled': 'unc+ ucr+ unr+', + 'Sensor Reading': '28 (+/- 0.500) degrees C', + 'Entity ID': '20.3 (Power Module)', + 'Assertions Enabled': 'unc+ ucr+ unr+', + 'Positive Hysteresis': '4.000', + 'Assertion Events': '', + 'Upper non-critical': '95.000', + 'Event Message Control': 'Per-threshold', + 'Upper non-recoverable': '105.000', + 'Normal Maximum': '112.000', + 'Maximum sensor range': 'Unspecified', + 'Sensor Type (Analog)': 'Temperature', + 'Readable Thresholds': 'unc ucr unr', + 'Negative Hysteresis': 'Unspecified', + 'Threshold Read Mask': 'unc ucr unr', + 'Upper critical': '100.000', + 'Sensor ID': 'DIMM AB VR Temp (0x38)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '16.000' + }, + 'PCI Riser 1 Temp (0x33)': { + 'Status': 'ok', + 'Deassertions Enabled': 'unc+ ucr+ unr+', + 'Sensor Reading': '38 (+/- 0) degrees C', + 'Entity ID': '16.1 (System Internal Expansion Board)', + 'Assertions Enabled': 'unc+ ucr+ unr+', + 'Positive Hysteresis': '4.000', + 'Assertion Events': '', + 'Upper non-critical': '70.000', + 'Event Message Control': 'Per-threshold', + 'Upper non-recoverable': '85.000', + 'Normal Maximum': '112.000', + 'Maximum sensor range': 'Unspecified', + 'Sensor Type (Analog)': 'Temperature', + 'Readable Thresholds': 'unc ucr unr', + 'Negative Hysteresis': 'Unspecified', + 'Threshold Read Mask': 'unc ucr unr', + 'Upper critical': '80.000', + 'Sensor ID': 'PCI Riser 1 Temp (0x33)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '16.000' + }, + }, + 'Current': { + 'Avg Power (0x2e)': { + 'Status': 'ok', + 'Sensor Reading': '130 (+/- 0) Watts', + 'Entity ID': '21.0 (Power Management)', + 'Assertions Enabled': '', + 'Event Message Control': 'Per-threshold', + 'Readable Thresholds': 'No Thresholds', + 'Positive Hysteresis': 'Unspecified', + 'Sensor Type (Analog)': 'Current', + 'Negative Hysteresis': 'Unspecified', + 'Maximum sensor range': 'Unspecified', + 'Sensor ID': 'Avg Power (0x2e)', + 'Assertion Events': '', + 'Minimum sensor range': '2550.000', + 'Settable Thresholds': 'No Thresholds' + } + }, + 'Fan': { + 'Fan 4A Tach (0x46)': { + 'Status': 'ok', + 'Sensor Reading': '6900 (+/- 0) RPM', + 'Entity ID': '29.4 (Fan Device)', + 'Assertions Enabled': 'lcr-', + 'Normal Minimum': '2580.000', + 'Positive Hysteresis': '120.000', + 'Assertion Events': '', + 'Event Message Control': 'Per-threshold', + 'Normal Maximum': '15300.000', + 'Deassertions Enabled': 'lcr-', + 'Sensor Type (Analog)': 'Fan', + 'Lower critical': '1920.000', + 'Negative Hysteresis': '120.000', + 'Threshold Read Mask': 'lcr', + 'Maximum sensor range': 'Unspecified', + 'Readable Thresholds': 'lcr', + 'Sensor ID': 'Fan 4A Tach (0x46)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '4020.000' + }, + 'Fan 5A Tach (0x48)': { + 'Status': 'ok', + 'Sensor Reading': '7140 (+/- 0) RPM', + 'Entity ID': '29.5 (Fan Device)', + 'Assertions Enabled': 'lcr-', + 'Normal Minimum': '2580.000', + 'Positive Hysteresis': '120.000', + 'Assertion Events': '', + 'Event Message Control': 'Per-threshold', + 'Normal Maximum': '15300.000', + 'Deassertions Enabled': 'lcr-', + 'Sensor Type (Analog)': 'Fan', + 'Lower critical': '1920.000', + 'Negative Hysteresis': '120.000', + 'Threshold Read Mask': 'lcr', + 'Maximum sensor range': 'Unspecified', + 'Readable Thresholds': 'lcr', + 'Sensor ID': 'Fan 5A Tach (0x48)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '4020.000' + }, + 'Fan 3A Tach (0x44)': { + 'Status': 'ok', + 'Sensor Reading': '6900 (+/- 0) RPM', + 'Entity ID': '29.3 (Fan Device)', + 'Assertions Enabled': 'lcr-', + 'Normal Minimum': '2580.000', + 'Positive Hysteresis': '120.000', + 'Assertion Events': '', + 'Event Message Control': 'Per-threshold', + 'Normal Maximum': '15300.000', + 'Deassertions Enabled': 'lcr-', + 'Sensor Type (Analog)': 'Fan', + 'Lower critical': '1920.000', + 'Negative Hysteresis': '120.000', + 'Threshold Read Mask': 'lcr', + 'Maximum sensor range': 'Unspecified', + 'Readable Thresholds': 'lcr', + 'Sensor ID': 'Fan 3A Tach (0x44)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '4020.000' + }, + 'Fan 1A Tach (0x40)': { + 'Status': 'ok', + 'Sensor Reading': '6960 (+/- 0) RPM', + 'Entity ID': '29.1 (Fan Device)', + 'Assertions Enabled': 'lcr-', + 'Normal Minimum': '2580.000', + 'Positive Hysteresis': '120.000', + 'Assertion Events': '', + 'Event Message Control': 'Per-threshold', + 'Normal Maximum': '15300.000', + 'Deassertions Enabled': 'lcr-', + 'Sensor Type (Analog)': 'Fan', + 'Lower critical': '1920.000', + 'Negative Hysteresis': '120.000', + 'Threshold Read Mask': 'lcr', + 'Maximum sensor range': 'Unspecified', + 'Readable Thresholds': 'lcr', + 'Sensor ID': 'Fan 1A Tach (0x40)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '4020.000' + }, + 'Fan 3B Tach (0x45)': { + 'Status': 'ok', + 'Sensor Reading': '7104 (+/- 0) RPM', + 'Entity ID': '29.3 (Fan Device)', + 'Assertions Enabled': 'lcr-', + 'Normal Minimum': '2752.000', + 'Positive Hysteresis': '128.000', + 'Assertion Events': '', + 'Event Message Control': 'Per-threshold', + 'Normal Maximum': '16320.000', + 'Deassertions Enabled': 'lcr-', + 'Sensor Type (Analog)': 'Fan', + 'Lower critical': '1920.000', + 'Negative Hysteresis': '128.000', + 'Threshold Read Mask': 'lcr', + 'Maximum sensor range': 'Unspecified', + 'Readable Thresholds': 'lcr', + 'Sensor ID': 'Fan 3B Tach (0x45)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '3968.000' + }, + 'Fan 2A Tach (0x42)': { + 'Status': 'ok', + 'Sensor Reading': '7080 (+/- 0) RPM', + 'Entity ID': '29.2 (Fan Device)', + 'Assertions Enabled': 'lcr-', + 'Normal Minimum': '2580.000', + 'Positive Hysteresis': '120.000', + 'Assertion Events': '', + 'Event Message Control': 'Per-threshold', + 'Normal Maximum': '15300.000', + 'Deassertions Enabled': 'lcr-', + 'Sensor Type (Analog)': 'Fan', + 'Lower critical': '1920.000', + 'Negative Hysteresis': '120.000', + 'Threshold Read Mask': 'lcr', + 'Maximum sensor range': 'Unspecified', + 'Readable Thresholds': 'lcr', + 'Sensor ID': 'Fan 2A Tach (0x42)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '4020.000' + }, + 'Fan 4B Tach (0x47)': { + 'Status': 'ok', + 'Sensor Reading': '7488 (+/- 0) RPM', + 'Entity ID': '29.4 (Fan Device)', + 'Assertions Enabled': 'lcr-', + 'Normal Minimum': '2752.000', + 'Positive Hysteresis': '128.000', + 'Assertion Events': '', + 'Event Message Control': 'Per-threshold', + 'Normal Maximum': '16320.000', + 'Deassertions Enabled': 'lcr-', + 'Sensor Type (Analog)': 'Fan', + 'Lower critical': '1920.000', + 'Negative Hysteresis': '128.000', + 'Threshold Read Mask': 'lcr', + 'Maximum sensor range': 'Unspecified', + 'Readable Thresholds': 'lcr', + 'Sensor ID': 'Fan 4B Tach (0x47)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '3968.000' + }, + 'Fan 2B Tach (0x43)': { + 'Status': 'ok', + 'Sensor Reading': '7168 (+/- 0) RPM', + 'Entity ID': '29.2 (Fan Device)', + 'Assertions Enabled': 'lcr-', + 'Normal Minimum': '2752.000', + 'Positive Hysteresis': '128.000', + 'Assertion Events': '', + 'Event Message Control': 'Per-threshold', + 'Normal Maximum': '16320.000', + 'Deassertions Enabled': 'lcr-', + 'Sensor Type (Analog)': 'Fan', + 'Lower critical': '1920.000', + 'Negative Hysteresis': '128.000', + 'Threshold Read Mask': 'lcr', + 'Maximum sensor range': 'Unspecified', + 'Readable Thresholds': 'lcr', + 'Sensor ID': 'Fan 2B Tach (0x43)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '3968.000' + }, + 'Fan 5B Tach (0x49)': { + 'Status': 'ok', + 'Sensor Reading': '7296 (+/- 0) RPM', + 'Entity ID': '29.5 (Fan Device)', + 'Assertions Enabled': 'lcr-', + 'Normal Minimum': '2752.000', + 'Positive Hysteresis': '128.000', + 'Assertion Events': '', + 'Event Message Control': 'Per-threshold', + 'Normal Maximum': '16320.000', + 'Deassertions Enabled': 'lcr-', + 'Sensor Type (Analog)': 'Fan', + 'Lower critical': '1920.000', + 'Negative Hysteresis': '128.000', + 'Threshold Read Mask': 'lcr', + 'Maximum sensor range': 'Unspecified', + 'Readable Thresholds': 'lcr', + 'Sensor ID': 'Fan 5B Tach (0x49)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '3968.000' + }, + 'Fan 1B Tach (0x41)': { + 'Status': 'ok', + 'Sensor Reading': '7296 (+/- 0) RPM', + 'Entity ID': '29.1 (Fan Device)', + 'Assertions Enabled': 'lcr-', + 'Normal Minimum': '2752.000', + 'Positive Hysteresis': '128.000', + 'Assertion Events': '', + 'Event Message Control': 'Per-threshold', + 'Normal Maximum': '16320.000', + 'Deassertions Enabled': 'lcr-', + 'Sensor Type (Analog)': 'Fan', + 'Lower critical': '1920.000', + 'Negative Hysteresis': '128.000', + 'Threshold Read Mask': 'lcr', + 'Maximum sensor range': 'Unspecified', + 'Readable Thresholds': 'lcr', + 'Sensor ID': 'Fan 1B Tach (0x41)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '3968.000' + }, + 'Fan 6B Tach (0x4b)': { + 'Status': 'ok', + 'Sensor Reading': '7616 (+/- 0) RPM', + 'Entity ID': '29.6 (Fan Device)', + 'Assertions Enabled': 'lcr-', + 'Normal Minimum': '2752.000', + 'Positive Hysteresis': '128.000', + 'Assertion Events': '', + 'Event Message Control': 'Per-threshold', + 'Normal Maximum': '16320.000', + 'Deassertions Enabled': 'lcr-', + 'Sensor Type (Analog)': 'Fan', + 'Lower critical': '1920.000', + 'Negative Hysteresis': '128.000', + 'Threshold Read Mask': 'lcr', + 'Maximum sensor range': 'Unspecified', + 'Readable Thresholds': 'lcr', + 'Sensor ID': 'Fan 6B Tach (0x4b)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '3968.000' + }, + 'Fan 6A Tach (0x4a)': { + 'Status': 'ok', + 'Sensor Reading': '7080 (+/- 0) RPM', + 'Entity ID': '29.6 (Fan Device)', + 'Assertions Enabled': 'lcr-', + 'Normal Minimum': '2580.000', + 'Positive Hysteresis': '120.000', + 'Assertion Events': '', + 'Event Message Control': 'Per-threshold', + 'Normal Maximum': '15300.000', + 'Deassertions Enabled': 'lcr-', + 'Sensor Type (Analog)': 'Fan', + 'Lower critical': '1920.000', + 'Negative Hysteresis': '120.000', + 'Threshold Read Mask': 'lcr', + 'Maximum sensor range': 'Unspecified', + 'Readable Thresholds': 'lcr', + 'Sensor ID': 'Fan 6A Tach (0x4a)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '4020.000' + } + }, + 'Voltage': { + 'Planar 12V (0x18)': { + 'Status': 'ok', + 'Sensor Reading': '12.312 (+/- 0) Volts', + 'Entity ID': '7.1 (System Board)', + 'Assertions Enabled': 'lcr- ucr+', + 'Event Message Control': 'Per-threshold', + 'Assertion Events': '', + 'Maximum sensor range': 'Unspecified', + 'Positive Hysteresis': '0.108', + 'Deassertions Enabled': 'lcr- ucr+', + 'Sensor Type (Analog)': 'Voltage', + 'Lower critical': '10.692', + 'Negative Hysteresis': '0.108', + 'Threshold Read Mask': 'lcr ucr', + 'Upper critical': '13.446', + 'Readable Thresholds': 'lcr ucr', + 'Sensor ID': 'Planar 12V (0x18)', + 'Settable Thresholds': 'lcr ucr', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '12.042' + }, + 'Planar 3.3V (0x16)': { + 'Status': 'ok', + 'Sensor Reading': '3.309 (+/- 0) Volts', + 'Entity ID': '7.1 (System Board)', + 'Assertions Enabled': 'lcr- ucr+', + 'Event Message Control': 'Per-threshold', + 'Assertion Events': '', + 'Maximum sensor range': 'Unspecified', + 'Positive Hysteresis': '0.028', + 'Deassertions Enabled': 'lcr- ucr+', + 'Sensor Type (Analog)': 'Voltage', + 'Lower critical': '3.039', + 'Negative Hysteresis': '0.028', + 'Threshold Read Mask': 'lcr ucr', + 'Upper critical': '3.564', + 'Readable Thresholds': 'lcr ucr', + 'Sensor ID': 'Planar 3.3V (0x16)', + 'Settable Thresholds': 'lcr ucr', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '3.309' + }, + 'Planar VBAT (0x1c)': { + 'Status': 'ok', + 'Sensor Reading': '3.137 (+/- 0) Volts', + 'Entity ID': '7.1 (System Board)', + 'Assertions Enabled': 'lnc- lcr-', + 'Event Message Control': 'Per-threshold', + 'Assertion Events': '', + 'Readable Thresholds': 'lcr lnc', + 'Positive Hysteresis': '0.025', + 'Deassertions Enabled': 'lnc- lcr-', + 'Sensor Type (Analog)': 'Voltage', + 'Lower critical': '2.095', + 'Negative Hysteresis': '0.025', + 'Lower non-critical': '2.248', + 'Maximum sensor range': 'Unspecified', + 'Sensor ID': 'Planar VBAT (0x1c)', + 'Settable Thresholds': 'lcr lnc', + 'Threshold Read Mask': 'lcr lnc', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '3.010' + }, + 'Planar 5V (0x17)': { + 'Status': 'ok', + 'Sensor Reading': '5.062 (+/- 0) Volts', + 'Entity ID': '7.1 (System Board)', + 'Assertions Enabled': 'lcr- ucr+', + 'Event Message Control': 'Per-threshold', + 'Assertion Events': '', + 'Maximum sensor range': 'Unspecified', + 'Positive Hysteresis': '0.045', + 'Deassertions Enabled': 'lcr- ucr+', + 'Sensor Type (Analog)': 'Voltage', + 'Lower critical': '4.475', + 'Negative Hysteresis': '0.045', + 'Threshold Read Mask': 'lcr ucr', + 'Upper critical': '5.582', + 'Readable Thresholds': 'lcr ucr', + 'Sensor ID': 'Planar 5V (0x17)', + 'Settable Thresholds': 'lcr ucr', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '4.995' + } + } + } + } +} + + +EMPTY_PAYLOAD = { + 'message_id': 'f22188ca-c068-47ce-a3e5-0e27ffe234c6', + 'publisher_id': 'f23188ca-c068-47ce-a3e5-0e27ffe234c6', + 'payload': { + 'instance_uuid': 'f11251ax-c568-25ca-4582-0x27add644c6', + 'timestamp': '20140223134852', + 'node_uuid': 'f4982fd2-2f2b-4bb5-9aff-48aac801d1ad', + 'event_type': 'hardware.ipmi.metrics.update', + 'payload': { + } + } +} + + +MISSING_SENSOR = { + 'message_id': 'f22188ca-c068-47ce-a3e5-0e27ffe234c6', + 'publisher_id': 'f23188ca-c068-47ce-a3e5-0e27ffe234c6', + 'payload': { + 'instance_uuid': 'f11251ax-c568-25ca-4582-0x27add644c6', + 'timestamp': '20140223134852', + 'node_uuid': 'f4982fd2-2f2b-4bb5-9aff-48aac801d1ad', + 'event_type': 'hardware.ipmi.metrics.update', + 'payload': { + 'Temperature': { + 'PCI Riser 1 Temp (0x33)': { + 'Status': 'ok', + 'Deassertions Enabled': 'unc+ ucr+ unr+', + 'Entity ID': '16.1 (System Internal Expansion Board)', + 'Assertions Enabled': 'unc+ ucr+ unr+', + 'Positive Hysteresis': '4.000', + 'Assertion Events': '', + 'Upper non-critical': '70.000', + 'Event Message Control': 'Per-threshold', + 'Upper non-recoverable': '85.000', + 'Normal Maximum': '112.000', + 'Maximum sensor range': 'Unspecified', + 'Sensor Type (Analog)': 'Temperature', + 'Readable Thresholds': 'unc ucr unr', + 'Negative Hysteresis': 'Unspecified', + 'Threshold Read Mask': 'unc ucr unr', + 'Upper critical': '80.000', + 'Sensor ID': 'PCI Riser 1 Temp (0x33)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '16.000' + }, + } + } + } +} + + +BAD_SENSOR = { + 'message_id': 'f22188ca-c068-47ce-a3e5-0e27ffe234c6', + 'publisher_id': 'f23188ca-c068-47ce-a3e5-0e27ffe234c6', + 'payload': { + 'instance_uuid': 'f11251ax-c568-25ca-4582-0x27add644c6', + 'timestamp': '20140223134852', + 'node_uuid': 'f4982fd2-2f2b-4bb5-9aff-48aac801d1ad', + 'event_type': 'hardware.ipmi.metrics.update', + 'payload': { + 'Temperature': { + 'PCI Riser 1 Temp (0x33)': { + 'Status': 'ok', + 'Deassertions Enabled': 'unc+ ucr+ unr+', + 'Sensor Reading': 'some bad stuff', + 'Entity ID': '16.1 (System Internal Expansion Board)', + 'Assertions Enabled': 'unc+ ucr+ unr+', + 'Positive Hysteresis': '4.000', + 'Assertion Events': '', + 'Upper non-critical': '70.000', + 'Event Message Control': 'Per-threshold', + 'Upper non-recoverable': '85.000', + 'Normal Maximum': '112.000', + 'Maximum sensor range': 'Unspecified', + 'Sensor Type (Analog)': 'Temperature', + 'Readable Thresholds': 'unc ucr unr', + 'Negative Hysteresis': 'Unspecified', + 'Threshold Read Mask': 'unc ucr unr', + 'Upper critical': '80.000', + 'Sensor ID': 'PCI Riser 1 Temp (0x33)', + 'Settable Thresholds': '', + 'Minimum sensor range': 'Unspecified', + 'Nominal Reading': '16.000' + }, + } + } + } +} + + +NO_SENSOR_ID = { + 'message_id': 'f22188ca-c068-47ce-a3e5-0e27ffe234c6', + 'publisher_id': 'f23188ca-c068-47ce-a3e5-0e27ffe234c6', + 'payload': { + 'instance_uuid': 'f11251ax-c568-25ca-4582-0x27add644c6', + 'timestamp': '20140223134852', + 'node_uuid': 'f4982fd2-2f2b-4bb5-9aff-48aac801d1ad', + 'event_type': 'hardware.ipmi.metrics.update', + 'payload': { + 'Temperature': { + 'PCI Riser 1 Temp (0x33)': { + 'Sensor Reading': '26 C', + }, + } + } + } +} + + +NO_NODE_ID = { + 'message_id': 'f22188ca-c068-47ce-a3e5-0e27ffe234c6', + 'publisher_id': 'f23188ca-c068-47ce-a3e5-0e27ffe234c6', + 'payload': { + 'instance_uuid': 'f11251ax-c568-25ca-4582-0x27add644c6', + 'timestamp': '20140223134852', + 'event_type': 'hardware.ipmi.metrics.update', + 'payload': { + 'Temperature': { + 'PCI Riser 1 Temp (0x33)': { + 'Sensor Reading': '26 C', + 'Sensor ID': 'PCI Riser 1 Temp (0x33)', + }, + } + } + } +} diff --git a/ceilometer/tests/hardware/notifications/test_ipmi.py b/ceilometer/tests/hardware/notifications/test_ipmi.py new file mode 100644 index 00000000..c1d7070c --- /dev/null +++ b/ceilometer/tests/hardware/notifications/test_ipmi.py @@ -0,0 +1,213 @@ +# +# Copyright 2014 Red Hat, Inc +# +# Author: Chris Dent +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +"""Tests for producing IPMI sample messages from notification events. +""" + +import mock + +from ceilometer.hardware.notifications import ipmi +from ceilometer.openstack.common import test +from ceilometer import sample +from ceilometer.tests.hardware.notifications import ipmi_test_data + + +class TestNotifications(test.BaseTestCase): + + def test_ipmi_temperature_notification(self): + """Test IPMI Temperature sensor data. + + Based on the above test data the expected sample for a single + temperature reading has:: + + * a resource_id composed from the node_uuid Sensor ID + * a name composed from 'hardware.ipmi.' and 'temperature' + * a volume from the first chunk of the Sensor Reading + * a unit from the last chunk of the Sensor Reading + * some readings are skipped if the value is 'Disabled' + """ + processor = ipmi.TemperatureSensorNotification(None) + counters = dict([(counter.resource_id, counter) for counter in + processor.process_notification( + ipmi_test_data.SENSOR_DATA)]) + + self.assertEqual(10, len(counters), + 'expected 10 temperature readings') + resource_id = ( + 'f4982fd2-2f2b-4bb5-9aff-48aac801d1ad-dimm_gh_vr_temp_(0x3b)' + ) + test_counter = counters[resource_id] + self.assertEqual(26.0, test_counter.volume) + self.assertEqual('C', test_counter.unit) + self.assertEqual(sample.TYPE_GAUGE, test_counter.type) + self.assertEqual('hardware.ipmi.temperature', test_counter.name) + self.assertEqual('hardware.ipmi.metrics.update', + test_counter.resource_metadata['event_type']) + + def test_ipmi_current_notification(self): + """Test IPMI Current sensor data. + + A single current reading is effectively the same as temperature, + modulo "current". + """ + processor = ipmi.CurrentSensorNotification(None) + counters = dict([(counter.resource_id, counter) for counter in + processor.process_notification( + ipmi_test_data.SENSOR_DATA)]) + + self.assertEqual(1, len(counters), 'expected 1 current reading') + resource_id = ( + 'f4982fd2-2f2b-4bb5-9aff-48aac801d1ad-avg_power_(0x2e)' + ) + test_counter = counters[resource_id] + self.assertEqual(130.0, test_counter.volume) + self.assertEqual('W', test_counter.unit) + self.assertEqual(sample.TYPE_GAUGE, test_counter.type) + self.assertEqual('hardware.ipmi.current', test_counter.name) + + def test_ipmi_fan_notification(self): + """Test IPMI Fan sensor data. + + A single fan reading is effectively the same as temperature, + modulo "fan". + """ + processor = ipmi.FanSensorNotification(None) + counters = dict([(counter.resource_id, counter) for counter in + processor.process_notification( + ipmi_test_data.SENSOR_DATA)]) + + self.assertEqual(12, len(counters), 'expected 12 fan readings') + resource_id = ( + 'f4982fd2-2f2b-4bb5-9aff-48aac801d1ad-fan_4a_tach_(0x46)' + ) + test_counter = counters[resource_id] + self.assertEqual(6900.0, test_counter.volume) + self.assertEqual('RPM', test_counter.unit) + self.assertEqual(sample.TYPE_GAUGE, test_counter.type) + self.assertEqual('hardware.ipmi.fan', test_counter.name) + + def test_ipmi_voltage_notification(self): + """Test IPMI Voltage sensor data. + + A single voltage reading is effectively the same as temperature, + modulo "voltage". + """ + processor = ipmi.VoltageSensorNotification(None) + counters = dict([(counter.resource_id, counter) for counter in + processor.process_notification( + ipmi_test_data.SENSOR_DATA)]) + + self.assertEqual(4, len(counters), 'expected 4 volate readings') + resource_id = ( + 'f4982fd2-2f2b-4bb5-9aff-48aac801d1ad-planar_vbat_(0x1c)' + ) + test_counter = counters[resource_id] + self.assertEqual(3.137, test_counter.volume) + self.assertEqual('V', test_counter.unit) + self.assertEqual(sample.TYPE_GAUGE, test_counter.type) + self.assertEqual('hardware.ipmi.voltage', test_counter.name) + + def test_disabed_skips_metric(self): + """Test that a meter which a disabled volume is skipped.""" + processor = ipmi.TemperatureSensorNotification(None) + counters = dict([(counter.resource_id, counter) for counter in + processor.process_notification( + ipmi_test_data.SENSOR_DATA)]) + + self.assertEqual(10, len(counters), + 'expected 10 temperature readings') + + resource_id = ( + 'f4982fd2-2f2b-4bb5-9aff-48aac801d1ad-mezz_card_temp_(0x35)' + ) + + self.assertNotIn(resource_id, counters) + + def test_empty_payload_no_metrics_success(self): + processor = ipmi.TemperatureSensorNotification(None) + counters = dict([(counter.resource_id, counter) for counter in + processor.process_notification( + ipmi_test_data.EMPTY_PAYLOAD)]) + + self.assertEqual(0, len(counters), 'expected 0 readings') + + @mock.patch('ceilometer.hardware.notifications.ipmi.LOG') + def test_missing_sensor_data(self, mylog): + processor = ipmi.TemperatureSensorNotification(None) + + messages = [] + mylog.warn = lambda *args: messages.extend(args) + + list(processor.process_notification(ipmi_test_data.MISSING_SENSOR)) + + self.assertEqual( + 'invalid sensor data for ' + 'f4982fd2-2f2b-4bb5-9aff-48aac801d1ad-pci_riser_1_temp_(0x33): ' + "missing 'Sensor Reading' in payload", + messages[0] + ) + + @mock.patch('ceilometer.hardware.notifications.ipmi.LOG') + def test_sensor_data_malformed(self, mylog): + processor = ipmi.TemperatureSensorNotification(None) + + messages = [] + mylog.warn = lambda *args: messages.extend(args) + + list(processor.process_notification(ipmi_test_data.BAD_SENSOR)) + + self.assertEqual( + 'invalid sensor data for ' + 'f4982fd2-2f2b-4bb5-9aff-48aac801d1ad-pci_riser_1_temp_(0x33): ' + 'unable to parse sensor reading: some bad stuff', + messages[0] + ) + + @mock.patch('ceilometer.hardware.notifications.ipmi.LOG') + def test_missing_node_uuid(self, mylog): + """Test for desired error message when 'node_uuid' missing. + + Presumably this will never happen given the way the data + is created, but better defensive than dead. + """ + processor = ipmi.TemperatureSensorNotification(None) + + messages = [] + mylog.warn = lambda *args: messages.extend(args) + + list(processor.process_notification(ipmi_test_data.NO_NODE_ID)) + + self.assertEqual( + 'invalid sensor data for missing id: missing key in payload: ' + "'node_uuid'", + messages[0] + ) + + @mock.patch('ceilometer.hardware.notifications.ipmi.LOG') + def test_missing_sensor_id(self, mylog): + """Test for desired error message when 'Sensor ID' missing.""" + processor = ipmi.TemperatureSensorNotification(None) + + messages = [] + mylog.warn = lambda *args: messages.extend(args) + + list(processor.process_notification(ipmi_test_data.NO_SENSOR_ID)) + + self.assertEqual( + 'invalid sensor data for missing id: missing key in payload: ' + "'Sensor ID'", + messages[0] + ) diff --git a/doc/source/measurements.rst b/doc/source/measurements.rst index 23ca4b20..776a5661 100644 --- a/doc/source/measurements.rst +++ b/doc/source/measurements.rst @@ -281,6 +281,23 @@ network.services.lb.incoming.bytes Cumulative B pool ID p network.services.lb.outgoing.bytes Cumulative B pool ID pollster Number of outgoing Bytes ======================================= ========== ========== ========== ========= ============================== +Ironic Hardware IPMI Sensor Data +================================ + +IPMI sensor data is not available by default in Ironic. To enable these meters +see the `Ironic Installation Guide`_. + +.. _Ironic Installation Guide: http://docs.openstack.org/developer/ironic/deploy/install-guide.html + +============================= ========== ====== ============== ============ ========================== +Meter Type Unit Resource Origin Note +============================= ========== ====== ============== ============ ========================== +hardware.ipmi.fan Gauge RPM fan sensor notification Fan RPM +hardware.ipmi.temperature Gauge C temp sensor notification Sensor Temperature Reading +hardware.ipmi.current Gauge W current sensor notification Sensor Current Reading +hardware.ipmi.voltage Gauge V voltage sensor notification Sensor Voltage Reading +============================= ========== ====== ============== ============ ========================== + Dynamically retrieving the Meters via ceilometer client ======================================================= diff --git a/setup.cfg b/setup.cfg index 8b85e9b5..c16d67eb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -66,6 +66,10 @@ ceilometer.notification = http.response = ceilometer.middleware:HTTPResponse stack_crud = ceilometer.orchestration.notifications:StackCRUD profiler = ceilometer.profiler.notifications:ProfilerNotifications + hardware.ipmi.temperature = ceilometer.hardware.notifications.ipmi:TemperatureSensorNotification + hardware.ipmi.voltage = ceilometer.hardware.notifications.ipmi:VoltageSensorNotification + hardware.ipmi.current = ceilometer.hardware.notifications.ipmi:CurrentSensorNotification + hardware.ipmi.fan = ceilometer.hardware.notifications.ipmi:FanSensorNotification ceilometer.discover = local_instances = ceilometer.compute.discovery:InstanceDiscovery