diff options
57 files changed, 3243 insertions, 888 deletions
diff --git a/ceilometer/agent.py b/ceilometer/agent.py index 3a60e62d..f906e019 100644 --- a/ceilometer/agent.py +++ b/ceilometer/agent.py @@ -204,7 +204,7 @@ class AgentManager(os_service.Service): discoverer = self._discoverer(name) if discoverer: try: - discovered = discoverer.discover(param) + discovered = discoverer.discover(self, param) partitioned = self.partition_coordinator.extract_my_subset( self._construct_group_id(discoverer.group_id), discovered) diff --git a/ceilometer/alarm/service.py b/ceilometer/alarm/service.py index c2687dfb..04e495b0 100644 --- a/ceilometer/alarm/service.py +++ b/ceilometer/alarm/service.py @@ -232,13 +232,15 @@ class AlarmNotifierService(os_service.Service): EXTENSIONS_NAMESPACE = "ceilometer.alarm.notifier" + notifiers = extension.ExtensionManager(EXTENSIONS_NAMESPACE, + invoke_on_load=True) + notifiers_schemas = notifiers.map(lambda x: x.name) + def __init__(self): super(AlarmNotifierService, self).__init__() transport = messaging.get_transport() self.rpc_server = messaging.get_rpc_server( transport, cfg.CONF.alarm.notifier_rpc_topic, self) - self.notifiers = extension.ExtensionManager(self.EXTENSIONS_NAMESPACE, - invoke_on_load=True) def start(self): super(AlarmNotifierService, self).start() diff --git a/ceilometer/api/controllers/v2.py b/ceilometer/api/controllers/v2.py index 436833d3..a00b5abd 100644 --- a/ceilometer/api/controllers/v2.py +++ b/ceilometer/api/controllers/v2.py @@ -37,6 +37,7 @@ import pytz import uuid from oslo.config import cfg +from oslo.utils import netutils from oslo.utils import strutils from oslo.utils import timeutils import pecan @@ -46,6 +47,7 @@ import wsme from wsme import types as wtypes import wsmeext.pecan as wsme_pecan +from ceilometer.alarm import service as alarm_service from ceilometer.alarm.storage import models as alarm_models from ceilometer.api import acl from ceilometer import messaging @@ -1325,8 +1327,12 @@ class ValidatedComplexQuery(object): if self.original_query.filter is wtypes.Unset: self.filter_expr = None else: - self.filter_expr = json.loads(self.original_query.filter) - self._validate_filter(self.filter_expr) + try: + self.filter_expr = json.loads(self.original_query.filter) + self._validate_filter(self.filter_expr) + except (ValueError, jsonschema.exceptions.ValidationError) as e: + raise ClientSideError(_("Filter expression not valid: %s") % + e.message) self._replace_isotime_with_datetime(self.filter_expr) self._convert_operator_to_lower_case(self.filter_expr) self._normalize_field_names_for_db_model(self.filter_expr) @@ -1336,8 +1342,12 @@ class ValidatedComplexQuery(object): if self.original_query.orderby is wtypes.Unset: self.orderby = None else: - self.orderby = json.loads(self.original_query.orderby) - self._validate_orderby(self.orderby) + try: + self.orderby = json.loads(self.original_query.orderby) + self._validate_orderby(self.orderby) + except (ValueError, jsonschema.exceptions.ValidationError) as e: + raise ClientSideError(_("Order-by expression not valid: %s") % + e.message) self._convert_orderby_to_lower_case(self.orderby) self._normalize_field_names_in_orderby(self.orderby) @@ -1807,6 +1817,7 @@ class Alarm(_Base): def validate(alarm): Alarm.check_rule(alarm) + Alarm.check_alarm_actions(alarm) if alarm.threshold_rule: # ensure an implicit constraint on project_id is added to # the query if not already present @@ -1845,6 +1856,25 @@ class Alarm(_Base): "cannot be set at the same time") raise ClientSideError(error) + @staticmethod + def check_alarm_actions(alarm): + actions_schema = alarm_service.AlarmNotifierService.notifiers_schemas + for state in state_kind: + actions_name = state.replace(" ", "_") + '_actions' + actions = getattr(alarm, actions_name) + if not actions: + continue + + for action in actions: + try: + url = netutils.urlsplit(action) + except Exception: + error = _("Unable to parse action %s") % action + raise ClientSideError(error) + if url.scheme not in actions_schema: + error = _("Unsupported action %s") % action + raise ClientSideError(error) + @classmethod def sample(cls): return cls(alarm_id=None, diff --git a/ceilometer/central/discovery.py b/ceilometer/central/discovery.py index f9fa4567..902f37ad 100644 --- a/ceilometer/central/discovery.py +++ b/ceilometer/central/discovery.py @@ -15,7 +15,6 @@ # License for the specific language governing permissions and limitations # under the License. -from keystoneclient.v2_0 import client as ksclient from oslo.config import cfg from ceilometer.openstack.common.gettextutils import _LW @@ -28,22 +27,11 @@ cfg.CONF.import_group('service_credentials', 'ceilometer.service') class EndpointDiscovery(plugin.DiscoveryBase): - def __init__(self): - super(EndpointDiscovery, self).__init__() - self.keystone = ksclient.Client( - username=cfg.CONF.service_credentials.os_username, - password=cfg.CONF.service_credentials.os_password, - tenant_id=cfg.CONF.service_credentials.os_tenant_id, - tenant_name=cfg.CONF.service_credentials.os_tenant_name, - cacert=cfg.CONF.service_credentials.os_cacert, - auth_url=cfg.CONF.service_credentials.os_auth_url, - region_name=cfg.CONF.service_credentials.os_region_name, - insecure=cfg.CONF.service_credentials.insecure) - def discover(self, param=None): + def discover(self, manager, param=None): if not param: return [] - endpoints = self.keystone.service_catalog.get_urls( + endpoints = manager.keystone.service_catalog.get_urls( service_type=param, endpoint_type=cfg.CONF.service_credentials.os_endpoint_type, region_name=cfg.CONF.service_credentials.os_region_name) diff --git a/ceilometer/cmd/agent_ipmi.py b/ceilometer/cmd/agent_ipmi.py new file mode 100644 index 00000000..eb57e75b --- /dev/null +++ b/ceilometer/cmd/agent_ipmi.py @@ -0,0 +1,23 @@ +# +# Copyright 2014 OpenStack Foundation +# +# 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. + +from ceilometer.ipmi import manager +from ceilometer.openstack.common import service as os_service +from ceilometer import service + + +def main(): + service.prepare_service() + os_service.launch(manager.AgentManager()).wait() diff --git a/ceilometer/compute/discovery.py b/ceilometer/compute/discovery.py index 7ec17526..01f8f083 100644 --- a/ceilometer/compute/discovery.py +++ b/ceilometer/compute/discovery.py @@ -34,7 +34,7 @@ class InstanceDiscovery(plugin.DiscoveryBase): super(InstanceDiscovery, self).__init__() self.nova_cli = nova_client.Client() - def discover(self, param=None): + def discover(self, manager, param=None): """Discover resources to monitor.""" instances = self.nova_cli.instance_get_all_by_host(cfg.CONF.host) return [i for i in instances diff --git a/ceilometer/hardware/discovery.py b/ceilometer/hardware/discovery.py index 8848eb57..3b7863a2 100644 --- a/ceilometer/hardware/discovery.py +++ b/ceilometer/hardware/discovery.py @@ -45,7 +45,7 @@ class NodesDiscoveryTripleO(plugin.DiscoveryBase): def _address(instance, field): return instance.addresses['ctlplane'][0].get(field) - def discover(self, param=None): + def discover(self, manager, param=None): """Discover resources to monitor.""" instances = self.nova_cli.instance_get_all() diff --git a/ceilometer/hardware/notifications/__init__.py b/ceilometer/ipmi/__init__.py index e69de29b..e69de29b 100644 --- a/ceilometer/hardware/notifications/__init__.py +++ b/ceilometer/ipmi/__init__.py diff --git a/ceilometer/ipmi/manager.py b/ceilometer/ipmi/manager.py new file mode 100644 index 00000000..6fbb1ec6 --- /dev/null +++ b/ceilometer/ipmi/manager.py @@ -0,0 +1,23 @@ +# Copyright 2014 Intel +# +# Author: Zhai Edwin <edwin.zhai@intel.com> +# +# 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. + +from ceilometer import agent + + +class AgentManager(agent.AgentManager): + + def __init__(self): + super(AgentManager, self).__init__('ipmi') diff --git a/ceilometer/tests/hardware/notifications/__init__.py b/ceilometer/ipmi/notifications/__init__.py index e69de29b..e69de29b 100644 --- a/ceilometer/tests/hardware/notifications/__init__.py +++ b/ceilometer/ipmi/notifications/__init__.py diff --git a/ceilometer/hardware/notifications/ipmi.py b/ceilometer/ipmi/notifications/ironic.py index deb69ce9..4e37eeaa 100644 --- a/ceilometer/hardware/notifications/ipmi.py +++ b/ceilometer/ipmi/notifications/ironic.py @@ -44,6 +44,25 @@ UNIT_MAP = { } +def validate_reading(data): + """Some sensors read "Disabled".""" + return data != 'Disabled' + + +def transform_id(data): + return data.lower().replace(' ', '_') + + +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) + + class InvalidSensorData(ValueError): pass @@ -76,25 +95,6 @@ class SensorNotification(plugin.NotificationBase): 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): # NOTE(chdent): How much of the payload should we keep? info = {'publisher_id': message['publisher_id'], @@ -125,7 +125,7 @@ class SensorNotification(plugin.NotificationBase): try: resource_id = '%(nodeid)s-%(sensorid)s' % { 'nodeid': message['payload']['node_uuid'], - 'sensorid': self._transform_id(payload['Sensor ID']) + 'sensorid': transform_id(payload['Sensor ID']) } except KeyError as exc: raise InvalidSensorData('missing key in payload: %s' % exc) @@ -139,8 +139,8 @@ class SensorNotification(plugin.NotificationBase): "missing 'Sensor Reading' in payload" ) - if self._validate_reading(sensor_reading): - volume, unit = self._parse_reading(sensor_reading) + if validate_reading(sensor_reading): + volume, unit = parse_reading(sensor_reading) yield sample.Sample.from_notification( name='hardware.ipmi.%s' % self.metric.lower(), type=sample.TYPE_GAUGE, diff --git a/ceilometer/ipmi/platform/__init__.py b/ceilometer/ipmi/platform/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ceilometer/ipmi/platform/__init__.py diff --git a/ceilometer/ipmi/platform/exception.py b/ceilometer/ipmi/platform/exception.py new file mode 100644 index 00000000..0a7d4855 --- /dev/null +++ b/ceilometer/ipmi/platform/exception.py @@ -0,0 +1,24 @@ +# Copyright 2014 Intel Corporation. +# All Rights Reserved. +# +# Author: Zhai Edwin <edwin.zhai@intel.com> +# +# 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. + + +class NodeManagerException(Exception): + pass + + +class IPMIException(Exception): + pass diff --git a/ceilometer/ipmi/platform/intel_node_manager.py b/ceilometer/ipmi/platform/intel_node_manager.py new file mode 100644 index 00000000..97ca0c16 --- /dev/null +++ b/ceilometer/ipmi/platform/intel_node_manager.py @@ -0,0 +1,274 @@ +# Copyright 2014 Intel Corporation. +# All Rights Reserved. +# +# Author: Zhai Edwin <edwin.zhai@intel.com> +# Author: Gao Fengqian <fengqian.gao@intel.com> +# +# 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. + +"""Node manager engine to collect power and temperature of compute node. + +Intel Node Manager Technology enables the datacenter IT to monitor and control +actual server power, thermal and compute utlization behavior through industry +defined standard IPMI. This file provides Node Manager engine to get simple +system power and temperature data based on ipmitool. +""" + +import binascii +import tempfile +import time + +from ceilometer.ipmi.platform import exception as nmexcept +from ceilometer.ipmi.platform import ipmitool +from ceilometer.openstack.common.gettextutils import _ +from oslo.config import cfg + +try: + import collections as ordereddict +except ImportError: + import ordereddict + +node_manager_init_retry = cfg.IntOpt('node_manager_init_retry', + default=3, + help='Number of retries upon Intel Node ' + 'Manager initialization failure') + + +CONF = cfg.CONF +CONF.register_opt(node_manager_init_retry, group='ipmi') + +IPMICMD = {"sdr_dump": "sdr dump", + "sdr_info": "sdr info", + "sensor_dump": "sdr -v"} +IPMIRAWCMD = {"get_device_id": "raw 0x06 0x01", + "init_sensor_agent": "raw 0x0a 0x2c 0x01", + "init_complete": "raw 0x0a 0x2c 0x00", + "init_sensor_agent_status": "raw 0x0a 0x2c 0x00", + "read_power_all": "raw 0x2e 0xc8 0x57 0x01 0x00 0x01 0x00 0x00", + "read_temperature_all": + "raw 0x2e 0xc8 0x57 0x01 0x00 0x02 0x00 0x00"} + +MANUFACTURER_ID_INTEL = ['57', '01', '00'] +INTEL_PREFIX = '5701000d01' + +# The template dict are made according to the spec. It contains the expected +# length of each item. And it can be used to parse the output of IPMI command. + +ONE_RETURN_TEMPLATE = {"ret": 1} + +BMC_INFO_TEMPLATE = ordereddict.OrderedDict() +BMC_INFO_TEMPLATE['Device_ID'] = 1 +BMC_INFO_TEMPLATE['Device_Revision'] = 1 +BMC_INFO_TEMPLATE['Firmware_Revision_1'] = 1 +BMC_INFO_TEMPLATE['Firmware_Revision_2'] = 1 +BMC_INFO_TEMPLATE['IPMI_Version'] = 1 +BMC_INFO_TEMPLATE['Additional_Device_support'] = 1 +BMC_INFO_TEMPLATE['Manufacturer_ID'] = 3 +BMC_INFO_TEMPLATE['Product_ID'] = 2 +BMC_INFO_TEMPLATE['Auxiliary_Firmware_Revision'] = 4 + +NM_STATISTICS_TEMPLATE = ordereddict.OrderedDict() +NM_STATISTICS_TEMPLATE['Manufacturer_ID'] = 3 +NM_STATISTICS_TEMPLATE['Current_value'] = 2 +NM_STATISTICS_TEMPLATE['Minimum_value'] = 2 +NM_STATISTICS_TEMPLATE['Maximum_value'] = 2 +NM_STATISTICS_TEMPLATE['Average_value'] = 2 +NM_STATISTICS_TEMPLATE['Time_stamp'] = 4 +NM_STATISTICS_TEMPLATE['Report_period'] = 4 +NM_STATISTICS_TEMPLATE["DomainID_PolicyState"] = 1 + +NM_GET_DEVICE_ID_TEMPLATE = ordereddict.OrderedDict() +NM_GET_DEVICE_ID_TEMPLATE['Device_ID'] = 1 +NM_GET_DEVICE_ID_TEMPLATE['Device_revision'] = 1 +NM_GET_DEVICE_ID_TEMPLATE['Firmware_revision_1'] = 1 +NM_GET_DEVICE_ID_TEMPLATE['Firmware_Revision_2'] = 1 +NM_GET_DEVICE_ID_TEMPLATE['IPMI_Version'] = 1 +NM_GET_DEVICE_ID_TEMPLATE['Additinal_Device_support'] = 1 +NM_GET_DEVICE_ID_TEMPLATE['Manufacturer_ID'] = 3 +NM_GET_DEVICE_ID_TEMPLATE['Product_ID_min_version'] = 1 +NM_GET_DEVICE_ID_TEMPLATE['Product_ID_major_version'] = 1 +NM_GET_DEVICE_ID_TEMPLATE['Implemented_firmware'] = 1 +NM_GET_DEVICE_ID_TEMPLATE['Firmware_build_number'] = 1 +NM_GET_DEVICE_ID_TEMPLATE['Last_digit_firmware_build_number'] = 1 +NM_GET_DEVICE_ID_TEMPLATE['Image_flags'] = 1 + + +def _hex(list=[]): + """Format the return value in list into hex.""" + if list: + list.reverse() + return int(''.join(list), 16) + + return 0 + + +class NodeManager(object): + """The python implementation of Intel Node Manager engine using ipmitool + + The class implements the engine to read power and temperature of + compute node. It uses ipmitool to execute the IPMI command and parse + the output into dict. + """ + _inited = False + _instance = None + + def __new__(cls, *args, **kwargs): + """Singleton to avoid duplicated initialization.""" + if not cls._instance: + cls._instance = super(NodeManager, cls).__new__(cls, *args, + **kwargs) + return cls._instance + + def __init__(self): + if not (self._instance and self._inited): + self.nm_support = False + self.channel_slave = '' + self._inited = True + + self.nm_support = self.check_node_manager() + + @staticmethod + def _parse_slave_and_channel(file_path): + """Parse the dumped file to get slave address and channel number. + + :param file_path: file path of dumped SDR file. + :return: slave address and channel number of target device. + """ + ret = None + prefix = INTEL_PREFIX + # According to Intel Node Manager spec, section 4.5, for Intel NM + # discovery OEM SDR records are type C0h. It contains manufacture ID + # and OEM data in the record body. + # 0-2 bytes are OEM ID, byte 3 is 0Dh and byte 4 is 01h. Byte 5, 6 + # is Intel NM device slave address and channel number/sensor owner LUN. + with open(file_path, 'rb') as bin_fp: + for line in bin_fp.readlines(): + if line: + data_str = binascii.hexlify(line) + if prefix in data_str: + oem_id_index = data_str.index(prefix) + ret = data_str[oem_id_index + len(prefix): + oem_id_index + len(prefix) + 4] + # Byte 5 is slave address. [7:4] from byte 6 is channel + # number, so just pick ret[2] here. + ret = (ret[0:2], ret[2]) + break + return ret + + @ipmitool.execute_ipmi_cmd(BMC_INFO_TEMPLATE) + def get_device_id(self): + """IPMI command GET_DEVICE_ID.""" + return IPMIRAWCMD["get_device_id"] + + @ipmitool.execute_ipmi_cmd(ONE_RETURN_TEMPLATE) + def _init_sensor_agent(self): + """Run initialization agent.""" + return IPMIRAWCMD["init_sensor_agent"] + + @ipmitool.execute_ipmi_cmd(ONE_RETURN_TEMPLATE) + def _init_sensor_agent_process(self): + """Check the status of initialization agent.""" + return IPMIRAWCMD["init_sensor_agent_status"] + + @ipmitool.execute_ipmi_cmd() + def _dump_sdr_file(self, data_file=""): + """Dump SDR into a file.""" + return IPMICMD["sdr_dump"] + " " + data_file + + @ipmitool.execute_ipmi_cmd(NM_GET_DEVICE_ID_TEMPLATE) + def _node_manager_get_device_id(self): + """GET_DEVICE_ID command in Intel Node Manager + + Different from IPMI command GET_DEVICE_ID, it contains more information + of Intel Node Manager. + """ + return self.channel_slave + ' ' + IPMIRAWCMD["get_device_id"] + + @ipmitool.execute_ipmi_cmd(NM_STATISTICS_TEMPLATE) + def _read_power_all(self): + """Get the power consumption of the whole platform.""" + return self.channel_slave + ' ' + IPMIRAWCMD['read_power_all'] + + @ipmitool.execute_ipmi_cmd(NM_STATISTICS_TEMPLATE) + def _read_temperature_all(self): + """Get the temperature info of the whole platform.""" + return self.channel_slave + ' ' + IPMIRAWCMD['read_temperature_all'] + + def read_power_all(self): + if self.nm_support: + return self._read_power_all() + + return {} + + def read_temperature_all(self): + if self.nm_support: + return self._read_temperature_all() + + return {} + + def init_node_manager(self): + if self._init_sensor_agent_process()['ret'] == ['01']: + return + # Run sensor initialization agent + for i in range(CONF.ipmi.node_manager_init_retry): + self._init_sensor_agent() + time.sleep(1) + if self._init_sensor_agent_process()['ret'] == ['01']: + return + + raise nmexcept.NodeManagerException(_('Node Manager init failed')) + + def discover_slave_channel(self): + """Discover target slave address and channel number.""" + file_path = tempfile.mkstemp()[1] + self._dump_sdr_file(data_file=file_path) + ret = self._parse_slave_and_channel(file_path) + slave_address = ''.join(['0x', ret[0]]) + channel = ''.join(['0x', ret[1]]) + # String of channel and slave_address + self.channel_slave = '-b ' + channel + ' -t ' + slave_address + + def node_manager_support(self): + """Intel Node Manager capability checking + + This function is used to detect if compute node support Intel + Node Manager or not and parse out the slave address and channel + number of node manager. + """ + self.manufacturer_id = self.get_device_id()['Manufacturer_ID'] + if MANUFACTURER_ID_INTEL != self.manufacturer_id: + # If the manufacturer is not Intel, just set False and return. + return False + + self.discover_slave_channel() + support = self._node_manager_get_device_id()['Implemented_firmware'] + # According to Intel Node Manager spec, return value of GET_DEVICE_ID, + # bits 3 to 0 shows if Intel NM implemented or not. + if int(support[0], 16) & 0xf != 0: + return True + else: + return False + + def check_node_manager(self): + """Intel Node Manager init and check + + This function is used to initialize Intel Node Manager and check the + capability without throwing exception. It's safe to call it on + non-NodeManager platform. + """ + try: + self.init_node_manager() + has_nm = self.node_manager_support() + except (nmexcept.NodeManagerException, nmexcept.IPMIException): + return False + return has_nm diff --git a/ceilometer/ipmi/platform/ipmi_sensor.py b/ceilometer/ipmi/platform/ipmi_sensor.py new file mode 100644 index 00000000..9749c485 --- /dev/null +++ b/ceilometer/ipmi/platform/ipmi_sensor.py @@ -0,0 +1,115 @@ +# Copyright 2014 Intel Corporation. +# +# Author: Zhai Edwin <edwin.zhai@intel.com> +# +# 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. + +"""IPMI sensor to collect various sensor data of compute node""" + +from ceilometer.ipmi.platform import exception as ipmiexcept +from ceilometer.ipmi.platform import ipmitool +from ceilometer.openstack.common.gettextutils import _ + +IPMICMD = {"sdr_dump": "sdr dump", + "sdr_info": "sdr info", + "sensor_dump": "sdr -v", + "sensor_dump_temperature": "sdr -v type Temperature", + "sensor_dump_current": "sdr -v type Current", + "sensor_dump_fan": "sdr -v type Fan", + "sensor_dump_voltage": "sdr -v type Voltage"} + +# Requires translation of output into dict +DICT_TRANSLATE_TEMPLATE = {"translate": 1} + + +class IPMISensor(object): + """The python implementation of IPMI sensor using ipmitool + + The class implements the IPMI sensor to get various sensor data of + compute node. It uses ipmitool to execute the IPMI command and parse + the output into dict. + """ + _inited = False + _instance = None + + def __new__(cls, *args, **kwargs): + """Singleton to avoid duplicated initialization.""" + if not cls._instance: + cls._instance = super(IPMISensor, cls).__new__(cls, *args, + **kwargs) + return cls._instance + + def __init__(self): + if not (self._instance and self._inited): + self.ipmi_support = False + self._inited = True + + self.ipmi_support = self.check_ipmi() + + @ipmitool.execute_ipmi_cmd() + def _get_sdr_info(self): + """Get the SDR info.""" + return IPMICMD['sdr_info'] + + @ipmitool.execute_ipmi_cmd(DICT_TRANSLATE_TEMPLATE) + def _read_sensor_all(self): + """Get the sensor data for type.""" + return IPMICMD['sensor_dump'] + + @ipmitool.execute_ipmi_cmd(DICT_TRANSLATE_TEMPLATE) + def _read_sensor_temperature(self): + """Get the sensor data for Temperature.""" + return IPMICMD['sensor_dump_temperature'] + + @ipmitool.execute_ipmi_cmd(DICT_TRANSLATE_TEMPLATE) + def _read_sensor_voltage(self): + """Get the sensor data for Voltage.""" + return IPMICMD['sensor_dump_voltage'] + + @ipmitool.execute_ipmi_cmd(DICT_TRANSLATE_TEMPLATE) + def _read_sensor_current(self): + """Get the sensor data for Current.""" + return IPMICMD['sensor_dump_current'] + + @ipmitool.execute_ipmi_cmd(DICT_TRANSLATE_TEMPLATE) + def _read_sensor_fan(self): + """Get the sensor data for Fan.""" + return IPMICMD['sensor_dump_fan'] + + def read_sensor_any(self, sensor_type=''): + """Get the sensor data for type.""" + if not self.ipmi_support: + return {} + + mapping = {'': self._read_sensor_all, + 'Temperature': self._read_sensor_temperature, + 'Fan': self._read_sensor_fan, + 'Voltage': self._read_sensor_voltage, + 'Current': self._read_sensor_current} + + try: + return mapping[sensor_type]() + except KeyError: + raise ipmiexcept.IPMIException(_('Wrong sensor type')) + + def check_ipmi(self): + """IPMI capability checking + + This function is used to detect if compute node is IPMI capable + platform. Just run a simple IPMI command to get SDR info for check. + """ + try: + self._get_sdr_info() + except ipmiexcept.IPMIException: + return False + return True diff --git a/ceilometer/ipmi/platform/ipmitool.py b/ceilometer/ipmi/platform/ipmitool.py new file mode 100644 index 00000000..a222b5f0 --- /dev/null +++ b/ceilometer/ipmi/platform/ipmitool.py @@ -0,0 +1,132 @@ +# Copyright 2014 Intel Corp. +# +# Author: Zhai Edwin <edwin.zhai@intel.com> +# Author: whaom <whaom@cn.ibm.com> +# +# 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. + +"""Utils to run ipmitool for data collection""" + +from ceilometer.ipmi.platform import exception as ipmiexcept +from ceilometer.openstack.common.gettextutils import _ +from ceilometer.openstack.common import processutils +from ceilometer import utils + + +# Following 2 functions are copied from ironic project to handle ipmitool's +# sensor data output. Need code clean and sharing in future. +# Check ironic/drivers/modules/ipmitool.py + + +def _get_sensor_type(sensor_data_dict): + # Have only three sensor type name IDs: 'Sensor Type (Analog)' + # 'Sensor Type (Discrete)' and 'Sensor Type (Threshold)' + + for key in ('Sensor Type (Analog)', 'Sensor Type (Discrete)', + 'Sensor Type (Threshold)'): + try: + return sensor_data_dict[key].split(' ', 1)[0] + except KeyError: + continue + + raise ipmiexcept.IPMIException(_("parse IPMI sensor data failed," + "unknown sensor type")) + + +def _process_sensor(sensor_data): + sensor_data_fields = sensor_data.split('\n') + sensor_data_dict = {} + for field in sensor_data_fields: + if not field: + continue + kv_value = field.split(':') + if len(kv_value) != 2: + continue + sensor_data_dict[kv_value[0].strip()] = kv_value[1].strip() + + return sensor_data_dict + + +def _translate_output(output): + """Translate the return value into JSON dict + + :param output: output of the execution of IPMI command(sensor reading) + """ + sensors_data_dict = {} + + sensors_data_array = output.split('\n\n') + for sensor_data in sensors_data_array: + sensor_data_dict = _process_sensor(sensor_data) + if not sensor_data_dict: + continue + + sensor_type = _get_sensor_type(sensor_data_dict) + + # ignore the sensors which have no current 'Sensor Reading' data + sensor_id = sensor_data_dict['Sensor ID'] + if 'Sensor Reading' in sensor_data_dict: + sensors_data_dict.setdefault(sensor_type, + {})[sensor_id] = sensor_data_dict + + # get nothing, no valid sensor data + if not sensors_data_dict: + raise ipmiexcept.IPMIException(_("parse IPMI sensor data failed," + "No data retrieved from given input")) + return sensors_data_dict + + +def _parse_output(output, template): + """Parse the return value of IPMI command into dict + + :param output: output of the execution of IPMI command + :param template: a dict that contains the expected items of + IPMI command and its length. + """ + ret = {} + index = 0 + if not (output and template): + return ret + + if "translate" in template: + ret = _translate_output(output) + else: + output_list = output.strip().split(' ') + if sum(template.values()) != len(output_list): + raise ipmiexcept.IPMIException(_("ipmitool output " + "length mismatch")) + for item in template.items(): + index_end = index + item[1] + update_value = output_list[index: index_end] + ret[item[0]] = update_value + index = index_end + return ret + + +def execute_ipmi_cmd(template={}): + """Decorator for the execution of IPMI command. + + It parses the output of IPMI command into dictionary. + """ + def _execute_ipmi_cmd(f): + def _execute(self, **kwargs): + args = ['ipmitool'] + command = f(self, **kwargs) + args.extend(command.split(" ")) + try: + (out, __) = utils.execute(*args, run_as_root=True) + except processutils.ProcessExecutionError: + raise ipmiexcept.IPMIException(_("running ipmitool failure")) + return _parse_output(out, template) + return _execute + + return _execute_ipmi_cmd diff --git a/ceilometer/ipmi/pollsters/__init__.py b/ceilometer/ipmi/pollsters/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ceilometer/ipmi/pollsters/__init__.py diff --git a/ceilometer/ipmi/pollsters/node.py b/ceilometer/ipmi/pollsters/node.py new file mode 100644 index 00000000..a04c3750 --- /dev/null +++ b/ceilometer/ipmi/pollsters/node.py @@ -0,0 +1,77 @@ +# Copyright 2014 Intel +# +# Author: Zhai Edwin <edwin.zhai@intel.com> +# +# 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. + +import abc + +from oslo.config import cfg +from oslo.utils import timeutils +import six + +from ceilometer.ipmi.platform import intel_node_manager as node_manager +from ceilometer import plugin +from ceilometer import sample + +CONF = cfg.CONF +CONF.import_opt('host', 'ceilometer.service') + + +@six.add_metaclass(abc.ABCMeta) +class _Base(plugin.PollsterBase): + def __init__(self): + self.nodemanager = node_manager.NodeManager() + + @property + def default_discovery(self): + return None + + @abc.abstractmethod + def read_data(self): + """Return data sample for IPMI.""" + + def get_samples(self, manager, cache, resources): + stats = self.read_data() + + if stats: + data = node_manager._hex(stats["Current_value"]) + + yield sample.Sample( + name=self.NAME, + type=self.TYPE, + unit=self.UNIT, + volume=data, + user_id=None, + project_id=None, + resource_id=CONF.host, + timestamp=timeutils.utcnow().isoformat(), + resource_metadata=None) + + +class TemperaturePollster(_Base): + NAME = "hardware.ipmi.node.temperature" + TYPE = sample.TYPE_GAUGE + UNIT = "C" + + def read_data(self): + return self.nodemanager.read_temperature_all() + + +class PowerPollster(_Base): + NAME = "hardware.ipmi.node.power" + TYPE = sample.TYPE_GAUGE + UNIT = "W" + + def read_data(self): + return self.nodemanager.read_power_all() diff --git a/ceilometer/ipmi/pollsters/sensor.py b/ceilometer/ipmi/pollsters/sensor.py new file mode 100644 index 00000000..837aec3a --- /dev/null +++ b/ceilometer/ipmi/pollsters/sensor.py @@ -0,0 +1,97 @@ +# Copyright 2014 Intel +# +# Author: Zhai Edwin <edwin.zhai@intel.com> +# +# 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. + +from oslo.config import cfg +from oslo.utils import timeutils + +from ceilometer.ipmi.notifications import ironic as parser +from ceilometer.ipmi.platform import ipmi_sensor +from ceilometer import plugin +from ceilometer import sample + +CONF = cfg.CONF +CONF.import_opt('host', 'ceilometer.service') + + +class InvalidSensorData(ValueError): + pass + + +class SensorPollster(plugin.PollsterBase): + + METRIC = None + + def __init__(self): + self.ipmi = ipmi_sensor.IPMISensor() + + @property + def default_discovery(self): + return None + + def _get_sensor_types(self, data, sensor_type): + try: + return (sensor_type_data for _, sensor_type_data + in data[sensor_type].items()) + except KeyError: + return [] + + def get_samples(self, manager, cache, resources): + stats = self.ipmi.read_sensor_any(self.METRIC) + + sensor_type_data = self._get_sensor_types(stats, self.METRIC) + + for sensor_data in sensor_type_data: + try: + sensor_reading = sensor_data['Sensor Reading'] + except KeyError: + raise InvalidSensorData("missing 'Sensor Reading'") + + if not parser.validate_reading(sensor_reading): + continue + + volume, unit = parser.parse_reading(sensor_reading) + + resource_id = '%(host)s-%(sensor-id)s' % { + 'host': CONF.host, + 'sensor-id': parser.transform_id(sensor_data['Sensor ID']) + } + + yield sample.Sample( + name='hardware.ipmi.%s' % self.METRIC.lower(), + type=sample.TYPE_GAUGE, + unit=unit, + volume=volume, + user_id=None, + project_id=None, + resource_id=resource_id, + timestamp=timeutils.utcnow().isoformat(), + resource_metadata=None) + + +class TemperatureSensorPollster(SensorPollster): + METRIC = 'Temperature' + + +class CurrentSensorPollster(SensorPollster): + METRIC = 'Current' + + +class FanSensorPollster(SensorPollster): + METRIC = 'Fan' + + +class VoltageSensorPollster(SensorPollster): + METRIC = 'Voltage' diff --git a/ceilometer/network/services/discovery.py b/ceilometer/network/services/discovery.py index 710a9f04..87e646de 100644 --- a/ceilometer/network/services/discovery.py +++ b/ceilometer/network/services/discovery.py @@ -29,7 +29,7 @@ class _BaseServicesDiscovery(base_plugin.DiscoveryBase): class LBPoolsDiscovery(_BaseServicesDiscovery): @plugin.check_keystone('network') - def discover(self, param=None): + def discover(self, manager, param=None): """Discover resources to monitor.""" pools = self.neutron_cli.pool_get_all() @@ -39,7 +39,7 @@ class LBPoolsDiscovery(_BaseServicesDiscovery): class LBVipsDiscovery(_BaseServicesDiscovery): @plugin.check_keystone('network') - def discover(self, param=None): + def discover(self, manager, param=None): """Discover resources to monitor.""" vips = self.neutron_cli.vip_get_all() @@ -49,7 +49,7 @@ class LBVipsDiscovery(_BaseServicesDiscovery): class LBMembersDiscovery(_BaseServicesDiscovery): @plugin.check_keystone('network') - def discover(self, param=None): + def discover(self, manager, param=None): """Discover resources to monitor.""" members = self.neutron_cli.member_get_all() @@ -59,7 +59,7 @@ class LBMembersDiscovery(_BaseServicesDiscovery): class LBHealthMonitorsDiscovery(_BaseServicesDiscovery): @plugin.check_keystone('network') - def discover(self, param=None): + def discover(self, manager, param=None): """Discover resources to monitor.""" probes = self.neutron_cli.health_monitor_get_all() @@ -68,7 +68,7 @@ class LBHealthMonitorsDiscovery(_BaseServicesDiscovery): class VPNServicesDiscovery(_BaseServicesDiscovery): @plugin.check_keystone('network') - def discover(self, param=None): + def discover(self, manager, param=None): """Discover resources to monitor.""" vpnservices = self.neutron_cli.vpn_get_all() @@ -78,7 +78,7 @@ class VPNServicesDiscovery(_BaseServicesDiscovery): class IPSecConnectionsDiscovery(_BaseServicesDiscovery): @plugin.check_keystone('network') - def discover(self, param=None): + def discover(self, manager, param=None): """Discover resources to monitor.""" conns = self.neutron_cli.ipsec_site_connections_get_all() @@ -87,7 +87,7 @@ class IPSecConnectionsDiscovery(_BaseServicesDiscovery): class FirewallDiscovery(_BaseServicesDiscovery): @plugin.check_keystone('network') - def discover(self, param=None): + def discover(self, manager, param=None): """Discover resources to monitor.""" fw = self.neutron_cli.firewall_get_all() @@ -97,7 +97,7 @@ class FirewallDiscovery(_BaseServicesDiscovery): class FirewallPolicyDiscovery(_BaseServicesDiscovery): @plugin.check_keystone('network') - def discover(self, param=None): + def discover(self, manager, param=None): """Discover resources to monitor.""" return self.neutron_cli.fw_policy_get_all() diff --git a/ceilometer/objectstore/swift_middleware.py b/ceilometer/objectstore/swift_middleware.py index ce41711a..c655ae5a 100644 --- a/ceilometer/objectstore/swift_middleware.py +++ b/ceilometer/objectstore/swift_middleware.py @@ -40,19 +40,16 @@ before "proxy-server" and add the following filter in the file: """ from __future__ import absolute_import +import logging from oslo.utils import timeutils from ceilometer.openstack.common import context -from ceilometer.openstack.common import log from ceilometer import pipeline from ceilometer import sample from ceilometer import service -LOG = log.getLogger(__name__) - - class InputProxy(object): """File-like object that counts bytes read. @@ -94,6 +91,9 @@ class CeilometerMiddleware(object): "metadata_headers", "").split(",") if h.strip()] + self.logger = logging.getLogger('ceilometer') + self.logger.setLevel(getattr(logging, + conf.get('log_level', 'WARN').upper())) service.prepare_service([]) self.pipeline_manager = pipeline.setup_pipeline() @@ -132,7 +132,7 @@ class CeilometerMiddleware(object): input_proxy.bytes_received, bytes_sent) except Exception: - LOG.exception('Failed to publish samples') + self.logger.exception('Failed to publish samples') try: iterable = self.app(env, my_start_response) diff --git a/ceilometer/openstack/common/jsonutils.py b/ceilometer/openstack/common/jsonutils.py index 8f346b57..e0c68268 100644 --- a/ceilometer/openstack/common/jsonutils.py +++ b/ceilometer/openstack/common/jsonutils.py @@ -44,7 +44,13 @@ if sys.version_info < (2, 7): # simplejson module if available try: import simplejson as json - is_simplejson = True + # NOTE(mriedem): Make sure we have a new enough version of simplejson + # to support the namedobject_as_tuple argument. This can be removed + # in the Kilo release when python 2.6 support is dropped. + if 'namedtuple_as_object' in inspect.getargspec(json.dumps).args: + is_simplejson = True + else: + import json except ImportError: import json else: diff --git a/ceilometer/openstack/common/processutils.py b/ceilometer/openstack/common/processutils.py new file mode 100644 index 00000000..ebb3cb74 --- /dev/null +++ b/ceilometer/openstack/common/processutils.py @@ -0,0 +1,285 @@ +# Copyright 2011 OpenStack Foundation. +# All Rights Reserved. +# +# 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. + +""" +System-level utilities and helper functions. +""" + +import errno +import logging +import multiprocessing +import os +import random +import shlex +import signal + +from eventlet.green import subprocess +from eventlet import greenthread +import six + +from ceilometer.openstack.common.gettextutils import _ +from ceilometer.openstack.common import strutils + + +LOG = logging.getLogger(__name__) + + +class InvalidArgumentError(Exception): + def __init__(self, message=None): + super(InvalidArgumentError, self).__init__(message) + + +class UnknownArgumentError(Exception): + def __init__(self, message=None): + super(UnknownArgumentError, self).__init__(message) + + +class ProcessExecutionError(Exception): + def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None, + description=None): + self.exit_code = exit_code + self.stderr = stderr + self.stdout = stdout + self.cmd = cmd + self.description = description + + if description is None: + description = _("Unexpected error while running command.") + if exit_code is None: + exit_code = '-' + message = _('%(description)s\n' + 'Command: %(cmd)s\n' + 'Exit code: %(exit_code)s\n' + 'Stdout: %(stdout)r\n' + 'Stderr: %(stderr)r') % {'description': description, + 'cmd': cmd, + 'exit_code': exit_code, + 'stdout': stdout, + 'stderr': stderr} + super(ProcessExecutionError, self).__init__(message) + + +class NoRootWrapSpecified(Exception): + def __init__(self, message=None): + super(NoRootWrapSpecified, self).__init__(message) + + +def _subprocess_setup(): + # Python installs a SIGPIPE handler by default. This is usually not what + # non-Python subprocesses expect. + signal.signal(signal.SIGPIPE, signal.SIG_DFL) + + +def execute(*cmd, **kwargs): + """Helper method to shell out and execute a command through subprocess. + + Allows optional retry. + + :param cmd: Passed to subprocess.Popen. + :type cmd: string + :param process_input: Send to opened process. + :type process_input: string + :param env_variables: Environment variables and their values that + will be set for the process. + :type env_variables: dict + :param check_exit_code: Single bool, int, or list of allowed exit + codes. Defaults to [0]. Raise + :class:`ProcessExecutionError` unless + program exits with one of these code. + :type check_exit_code: boolean, int, or [int] + :param delay_on_retry: True | False. Defaults to True. If set to True, + wait a short amount of time before retrying. + :type delay_on_retry: boolean + :param attempts: How many times to retry cmd. + :type attempts: int + :param run_as_root: True | False. Defaults to False. If set to True, + the command is prefixed by the command specified + in the root_helper kwarg. + :type run_as_root: boolean + :param root_helper: command to prefix to commands called with + run_as_root=True + :type root_helper: string + :param shell: whether or not there should be a shell used to + execute this command. Defaults to false. + :type shell: boolean + :param loglevel: log level for execute commands. + :type loglevel: int. (Should be logging.DEBUG or logging.INFO) + :returns: (stdout, stderr) from process execution + :raises: :class:`UnknownArgumentError` on + receiving unknown arguments + :raises: :class:`ProcessExecutionError` + """ + + process_input = kwargs.pop('process_input', None) + env_variables = kwargs.pop('env_variables', None) + check_exit_code = kwargs.pop('check_exit_code', [0]) + ignore_exit_code = False + delay_on_retry = kwargs.pop('delay_on_retry', True) + attempts = kwargs.pop('attempts', 1) + run_as_root = kwargs.pop('run_as_root', False) + root_helper = kwargs.pop('root_helper', '') + shell = kwargs.pop('shell', False) + loglevel = kwargs.pop('loglevel', logging.DEBUG) + + if isinstance(check_exit_code, bool): + ignore_exit_code = not check_exit_code + check_exit_code = [0] + elif isinstance(check_exit_code, int): + check_exit_code = [check_exit_code] + + if kwargs: + raise UnknownArgumentError(_('Got unknown keyword args: %r') % kwargs) + + if run_as_root and hasattr(os, 'geteuid') and os.geteuid() != 0: + if not root_helper: + raise NoRootWrapSpecified( + message=_('Command requested root, but did not ' + 'specify a root helper.')) + cmd = shlex.split(root_helper) + list(cmd) + + cmd = map(str, cmd) + sanitized_cmd = strutils.mask_password(' '.join(cmd)) + + while attempts > 0: + attempts -= 1 + try: + LOG.log(loglevel, _('Running cmd (subprocess): %s'), sanitized_cmd) + _PIPE = subprocess.PIPE # pylint: disable=E1101 + + if os.name == 'nt': + preexec_fn = None + close_fds = False + else: + preexec_fn = _subprocess_setup + close_fds = True + + obj = subprocess.Popen(cmd, + stdin=_PIPE, + stdout=_PIPE, + stderr=_PIPE, + close_fds=close_fds, + preexec_fn=preexec_fn, + shell=shell, + env=env_variables) + result = None + for _i in six.moves.range(20): + # NOTE(russellb) 20 is an arbitrary number of retries to + # prevent any chance of looping forever here. + try: + if process_input is not None: + result = obj.communicate(process_input) + else: + result = obj.communicate() + except OSError as e: + if e.errno in (errno.EAGAIN, errno.EINTR): + continue + raise + break + obj.stdin.close() # pylint: disable=E1101 + _returncode = obj.returncode # pylint: disable=E1101 + LOG.log(loglevel, 'Result was %s' % _returncode) + if not ignore_exit_code and _returncode not in check_exit_code: + (stdout, stderr) = result + sanitized_stdout = strutils.mask_password(stdout) + sanitized_stderr = strutils.mask_password(stderr) + raise ProcessExecutionError(exit_code=_returncode, + stdout=sanitized_stdout, + stderr=sanitized_stderr, + cmd=sanitized_cmd) + return result + except ProcessExecutionError: + if not attempts: + raise + else: + LOG.log(loglevel, _('%r failed. Retrying.'), sanitized_cmd) + if delay_on_retry: + greenthread.sleep(random.randint(20, 200) / 100.0) + finally: + # NOTE(termie): this appears to be necessary to let the subprocess + # call clean something up in between calls, without + # it two execute calls in a row hangs the second one + greenthread.sleep(0) + + +def trycmd(*args, **kwargs): + """A wrapper around execute() to more easily handle warnings and errors. + + Returns an (out, err) tuple of strings containing the output of + the command's stdout and stderr. If 'err' is not empty then the + command can be considered to have failed. + + :discard_warnings True | False. Defaults to False. If set to True, + then for succeeding commands, stderr is cleared + + """ + discard_warnings = kwargs.pop('discard_warnings', False) + + try: + out, err = execute(*args, **kwargs) + failed = False + except ProcessExecutionError as exn: + out, err = '', six.text_type(exn) + failed = True + + if not failed and discard_warnings and err: + # Handle commands that output to stderr but otherwise succeed + err = '' + + return out, err + + +def ssh_execute(ssh, cmd, process_input=None, + addl_env=None, check_exit_code=True): + LOG.debug('Running cmd (SSH): %s', cmd) + if addl_env: + raise InvalidArgumentError(_('Environment not supported over SSH')) + + if process_input: + # This is (probably) fixable if we need it... + raise InvalidArgumentError(_('process_input not supported over SSH')) + + stdin_stream, stdout_stream, stderr_stream = ssh.exec_command(cmd) + channel = stdout_stream.channel + + # NOTE(justinsb): This seems suspicious... + # ...other SSH clients have buffering issues with this approach + stdout = stdout_stream.read() + stderr = stderr_stream.read() + stdin_stream.close() + + exit_status = channel.recv_exit_status() + + # exit_status == -1 if no exit code was returned + if exit_status != -1: + LOG.debug('Result was %s' % exit_status) + if check_exit_code and exit_status != 0: + raise ProcessExecutionError(exit_code=exit_status, + stdout=stdout, + stderr=stderr, + cmd=cmd) + + return (stdout, stderr) + + +def get_worker_count(): + """Utility to get the default worker count. + + @return: The number of CPUs if that can be determined, else a default + worker count of 1 is returned. + """ + try: + return multiprocessing.cpu_count() + except NotImplementedError: + return 1 diff --git a/ceilometer/openstack/common/strutils.py b/ceilometer/openstack/common/strutils.py index a5888db8..9814195d 100644 --- a/ceilometer/openstack/common/strutils.py +++ b/ceilometer/openstack/common/strutils.py @@ -50,26 +50,37 @@ SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]") SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+") -# NOTE(flaper87): The following 3 globals are used by `mask_password` +# NOTE(flaper87): The following globals are used by `mask_password` _SANITIZE_KEYS = ['adminPass', 'admin_pass', 'password', 'admin_password'] # NOTE(ldbragst): Let's build a list of regex objects using the list of # _SANITIZE_KEYS we already have. This way, we only have to add the new key # to the list of _SANITIZE_KEYS and we can generate regular expressions # for XML and JSON automatically. -_SANITIZE_PATTERNS = [] -_FORMAT_PATTERNS = [r'(%(key)s\s*[=]\s*[\"\']).*?([\"\'])', - r'(<%(key)s>).*?(</%(key)s>)', - r'([\"\']%(key)s[\"\']\s*:\s*[\"\']).*?([\"\'])', - r'([\'"].*?%(key)s[\'"]\s*:\s*u?[\'"]).*?([\'"])', - r'([\'"].*?%(key)s[\'"]\s*,\s*\'--?[A-z]+\'\s*,\s*u?[\'"])' - '.*?([\'"])', - r'(%(key)s\s*--?[A-z]+\s*)\S+(\s*)'] +_SANITIZE_PATTERNS_2 = [] +_SANITIZE_PATTERNS_1 = [] + +# NOTE(amrith): Some regular expressions have only one parameter, some +# have two parameters. Use different lists of patterns here. +_FORMAT_PATTERNS_1 = [r'(%(key)s\s*[=]\s*)[^\s^\'^\"]+'] +_FORMAT_PATTERNS_2 = [r'(%(key)s\s*[=]\s*[\"\']).*?([\"\'])', + r'(%(key)s\s+[\"\']).*?([\"\'])', + r'([-]{2}%(key)s\s+)[^\'^\"^=^\s]+([\s]*)', + r'(<%(key)s>).*?(</%(key)s>)', + r'([\"\']%(key)s[\"\']\s*:\s*[\"\']).*?([\"\'])', + r'([\'"].*?%(key)s[\'"]\s*:\s*u?[\'"]).*?([\'"])', + r'([\'"].*?%(key)s[\'"]\s*,\s*\'--?[A-z]+\'\s*,\s*u?' + '[\'"]).*?([\'"])', + r'(%(key)s\s*--?[A-z]+\s*)\S+(\s*)'] for key in _SANITIZE_KEYS: - for pattern in _FORMAT_PATTERNS: + for pattern in _FORMAT_PATTERNS_2: reg_ex = re.compile(pattern % {'key': key}, re.DOTALL) - _SANITIZE_PATTERNS.append(reg_ex) + _SANITIZE_PATTERNS_2.append(reg_ex) + + for pattern in _FORMAT_PATTERNS_1: + reg_ex = re.compile(pattern % {'key': key}, re.DOTALL) + _SANITIZE_PATTERNS_1.append(reg_ex) def int_from_bool_as_string(subject): @@ -289,7 +300,12 @@ def mask_password(message, secret="***"): if not any(key in message for key in _SANITIZE_KEYS): return message - secret = r'\g<1>' + secret + r'\g<2>' - for pattern in _SANITIZE_PATTERNS: - message = re.sub(pattern, secret, message) + substitute = r'\g<1>' + secret + r'\g<2>' + for pattern in _SANITIZE_PATTERNS_2: + message = re.sub(pattern, substitute, message) + + substitute = r'\g<1>' + secret + for pattern in _SANITIZE_PATTERNS_1: + message = re.sub(pattern, substitute, message) + return message diff --git a/ceilometer/plugin.py b/ceilometer/plugin.py index 46481da1..a0fb9fa1 100644 --- a/ceilometer/plugin.py +++ b/ceilometer/plugin.py @@ -163,9 +163,10 @@ class PollsterBase(PluginBase): @six.add_metaclass(abc.ABCMeta) class DiscoveryBase(object): @abc.abstractmethod - def discover(self, param=None): + def discover(self, manager, param=None): """Discover resources to monitor. + :param manager: The service manager class invoking the plugin. :param param: an optional parameter to guide the discovery """ diff --git a/ceilometer/storage/hbase/utils.py b/ceilometer/storage/hbase/utils.py index 09b4f58e..4a2a0b08 100644 --- a/ceilometer/storage/hbase/utils.py +++ b/ceilometer/storage/hbase/utils.py @@ -185,17 +185,39 @@ def make_query(metaquery=None, trait_query=None, **kwargs): return res_q -def get_meter_columns(metaquery, **kwargs): - """Return a list of required columns in meter table to be scanned . +def get_meter_columns(metaquery=None, need_timestamp=False, **kwargs): + """Return a list of required columns in meter table to be scanned. + + SingleColumnFilter has 'columns' filter that should be used to determine + what columns we are interested in. But if we want to use 'filter' and + 'columns' together we have to include columns we are filtering by + to columns list. + + Please see an example: If we make scan with filter + "SingleColumnValueFilter ('f', 's_test-1', =, 'binary:\"1\"')" + and columns ['f:rts'], the output will be always empty + because only 'rts' will be returned and filter will be applied + to this data so 's_test-1' cannot be find. + To make this request correct it should be fixed as follows: + filter = "SingleColumnValueFilter ('f', 's_test-1', =, 'binary:\"1\"')", + columns = ['f:rts','f:s_test-1']} :param metaquery: optional metaquery dict + :param need_timestamp: flag, which defines the need for timestamp columns :param kwargs: key-value pairs to filter on. Key should be a real column name in db """ columns = ['f:message', 'f:recorded_at'] - columns.extend("f:%s" % k for k, v in kwargs.items() if v) + columns.extend("f:%s" % k for k, v in kwargs.items() + if v is not None) if metaquery: - columns.extend("f:r_%s" % k for k, v in metaquery.items() if v) + columns.extend("f:r_%s" % k for k, v in metaquery.items() + if v is not None) + source = kwargs.get('source') + if source: + columns.append("f:s_%s" % source) + if need_timestamp: + columns.extend(['f:rts', 'f:timestamp']) return columns @@ -215,7 +237,6 @@ def make_sample_query_from_filter(sample_filter, require_meter=True): start=sample_filter.start, start_op=sample_filter.start_timestamp_op, end=sample_filter.end, end_op=sample_filter.end_timestamp_op, some_id=meter) - kwargs = dict(user_id=sample_filter.user, project_id=sample_filter.project, counter_name=meter, @@ -229,7 +250,10 @@ def make_sample_query_from_filter(sample_filter, require_meter=True): res_q = q + " AND " + ts_query if ts_query else q else: res_q = ts_query if ts_query else None - columns = get_meter_columns(metaquery=sample_filter.metaquery, **kwargs) + + need_timestamp = (sample_filter.start or sample_filter.end) is not None + columns = get_meter_columns(metaquery=sample_filter.metaquery, + need_timestamp=need_timestamp, **kwargs) return res_q, start_row, end_row, columns diff --git a/ceilometer/storage/impl_hbase.py b/ceilometer/storage/impl_hbase.py index 24b12bf4..fe483641 100644 --- a/ceilometer/storage/impl_hbase.py +++ b/ceilometer/storage/impl_hbase.py @@ -380,7 +380,7 @@ class Connection(base.Connection): (sample_filter, require_meter=False)) LOG.debug(_("Query Meter Table: %s") % q) gen = meter_table.scan(filter=q, row_start=start, row_stop=stop, - limit=limit) + limit=limit, columns=columns) for ignored, meter in gen: d_meter = hbase_utils.deserialize_entry(meter)[0] d_meter['message']['recorded_at'] = d_meter['recorded_at'] diff --git a/ceilometer/tests/agentbase.py b/ceilometer/tests/agentbase.py index bb4a6e39..a582109e 100644 --- a/ceilometer/tests/agentbase.py +++ b/ceilometer/tests/agentbase.py @@ -95,13 +95,13 @@ class TestPollsterException(TestPollster): class TestDiscovery(plugin.DiscoveryBase): - def discover(self, param=None): + def discover(self, manager, param=None): self.params.append(param) return self.resources class TestDiscoveryException(plugin.DiscoveryBase): - def discover(self, param=None): + def discover(self, manager, param=None): self.params.append(param) raise Exception() diff --git a/ceilometer/tests/api/v2/test_alarm_scenarios.py b/ceilometer/tests/api/v2/test_alarm_scenarios.py index cdb31990..2481259c 100644 --- a/ceilometer/tests/api/v2/test_alarm_scenarios.py +++ b/ceilometer/tests/api/v2/test_alarm_scenarios.py @@ -628,6 +628,64 @@ class TestAlarms(v2.FunctionalTest, 'not valid for this resource', resp.json['error_message']['faultstring']) + def _do_post_alarm_invalid_action(self, ok_actions=[], alarm_actions=[], + insufficient_data_actions=[], + error_message=None): + json = { + 'enabled': False, + 'name': 'added_alarm', + 'state': 'ok', + 'type': 'threshold', + 'ok_actions': ok_actions, + 'alarm_actions': alarm_actions, + 'insufficient_data_actions': insufficient_data_actions, + 'repeat_actions': True, + 'threshold_rule': { + 'meter_name': 'ameter', + 'query': [{'field': 'metadata.field', + 'op': 'eq', + 'value': '5', + 'type': 'string'}], + 'comparison_operator': 'le', + 'statistic': 'count', + 'threshold': 50, + 'evaluation_periods': '3', + 'period': '180', + } + } + resp = self.post_json('/alarms', params=json, status=400, + headers=self.auth_headers) + alarms = list(self.alarm_conn.get_alarms()) + self.assertEqual(4, len(alarms)) + self.assertEqual(error_message, + resp.json['error_message']['faultstring']) + + def test_post_invalid_alarm_ok_actions(self): + self._do_post_alarm_invalid_action( + ok_actions=['spam://something/ok'], + error_message='Unsupported action spam://something/ok') + + def test_post_invalid_alarm_alarm_actions(self): + self._do_post_alarm_invalid_action( + alarm_actions=['spam://something/alarm'], + error_message='Unsupported action spam://something/alarm') + + def test_post_invalid_alarm_insufficient_data_actions(self): + self._do_post_alarm_invalid_action( + insufficient_data_actions=['spam://something/insufficient'], + error_message='Unsupported action spam://something/insufficient') + + @staticmethod + def _fake_urlsplit(*args, **kwargs): + raise Exception("Evil urlsplit!") + + def test_post_invalid_alarm_actions_format(self): + with mock.patch('oslo.utils.netutils.urlsplit', + self._fake_urlsplit): + self._do_post_alarm_invalid_action( + alarm_actions=['http://[::1'], + error_message='Unable to parse action http://[::1') + def test_post_alarm_defaults(self): to_check = { 'enabled': True, @@ -1489,6 +1547,44 @@ class TestAlarms(v2.FunctionalTest, 'Alarm with name=name1 exists', resp.json['error_message']['faultstring']) + def test_put_invalid_alarm_actions(self): + json = { + 'enabled': False, + 'name': 'name1', + 'state': 'ok', + 'type': 'threshold', + 'ok_actions': ['spam://something/ok'], + 'alarm_actions': ['http://something/alarm'], + 'insufficient_data_actions': ['http://something/no'], + 'repeat_actions': True, + 'threshold_rule': { + 'meter_name': 'ameter', + 'query': [{'field': 'metadata.field', + 'op': 'eq', + 'value': '5', + 'type': 'string'}], + 'comparison_operator': 'le', + 'statistic': 'count', + 'threshold': 50, + 'evaluation_periods': 3, + 'period': 180, + } + } + data = self.get_json('/alarms', + q=[{'field': 'name', + 'value': 'name2', + }]) + self.assertEqual(1, len(data)) + alarm_id = data[0]['alarm_id'] + + resp = self.put_json('/alarms/%s' % alarm_id, + expect_errors=True, status=400, + params=json, + headers=self.auth_headers) + self.assertEqual( + 'Unsupported action spam://something/ok', + resp.json['error_message']['faultstring']) + def test_put_alarm_combination_cannot_specify_itself(self): json = { 'name': 'name4', diff --git a/ceilometer/tests/api/v2/test_complex_query_scenarios.py b/ceilometer/tests/api/v2/test_complex_query_scenarios.py index 0040d173..2daa602c 100644 --- a/ceilometer/tests/api/v2/test_complex_query_scenarios.py +++ b/ceilometer/tests/api/v2/test_complex_query_scenarios.py @@ -210,6 +210,24 @@ class TestQueryMetersController(tests_api.FunctionalTest, for sample_item in data.json: self.assertIn(sample_item['resource_id'], set(["resource-id2"])) + def test_query_with_wrong_field_name(self): + data = self.post_json(self.url, + params={"filter": + '{"=": {"unknown": "resource-id2"}}'}, + expect_errors=True) + + self.assertEqual(400, data.status_int) + self.assertIn("is not valid under any of the given schemas", data.body) + + def test_query_with_wrong_json(self): + data = self.post_json(self.url, + params={"filter": + '{"=": "resource": "resource-id2"}}'}, + expect_errors=True) + + self.assertEqual(400, data.status_int) + self.assertIn("Filter expression not valid", data.body) + def test_query_with_field_name_user(self): data = self.post_json(self.url, params={"filter": @@ -257,7 +275,16 @@ class TestQueryMetersController(tests_api.FunctionalTest, params={"orderby": '[{"project_id": ""}]'}, expect_errors=True) - self.assertEqual(500, data.status_int) + self.assertEqual(400, data.status_int) + self.assertIn("does not match '(?i)^asc$|^desc$'", data.body) + + def test_query_with_wrong_json_in_orderby(self): + data = self.post_json(self.url, + params={"orderby": '{"project_id": "desc"}]'}, + expect_errors=True) + + self.assertEqual(400, data.status_int) + self.assertIn("Order-by expression not valid: Extra data", data.body) def test_filter_with_metadata(self): data = self.post_json(self.url, diff --git a/ceilometer/tests/central/test_discovery.py b/ceilometer/tests/central/test_discovery.py new file mode 100644 index 00000000..eac489b5 --- /dev/null +++ b/ceilometer/tests/central/test_discovery.py @@ -0,0 +1,46 @@ +# +# Copyright 2014 Red Hat Inc. +# +# Author: Nejc Saje <nsaje@redhat.com> +# +# 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 ceilometer/central/manager.py +""" + +import mock +from oslo.config import fixture as fixture_config +from oslotest import base + +from ceilometer.central import discovery + + +class TestEndpointDiscovery(base.BaseTestCase): + + def setUp(self): + super(TestEndpointDiscovery, self).setUp() + self.discovery = discovery.EndpointDiscovery() + self.manager = mock.MagicMock() + self.CONF = self.useFixture(fixture_config.Config()).conf + + def test_keystone_called(self): + self.CONF.set_override('os_endpoint_type', 'test-endpoint-type', + group='service_credentials') + self.CONF.set_override('os_region_name', 'test-region-name', + group='service_credentials') + self.discovery.discover(self.manager, param='test-service-type') + expected = [mock.call(service_type='test-service-type', + endpoint_type='test-endpoint-type', + region_name='test-region-name')] + self.assertEqual(expected, + self.manager.keystone.service_catalog.get_urls + .call_args_list)
\ No newline at end of file diff --git a/ceilometer/tests/hardware/notifications/ipmi_test_data.py b/ceilometer/tests/hardware/notifications/ipmi_test_data.py deleted file mode 100644 index 6cd42059..00000000 --- a/ceilometer/tests/hardware/notifications/ipmi_test_data.py +++ /dev/null @@ -1,785 +0,0 @@ -# -# Copyright 2014 Red Hat, Inc -# -# Author: Chris Dent <chdent@redhat.com> -# -# 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/ipmi/__init__.py b/ceilometer/tests/ipmi/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ceilometer/tests/ipmi/__init__.py diff --git a/ceilometer/tests/ipmi/notifications/__init__.py b/ceilometer/tests/ipmi/notifications/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ceilometer/tests/ipmi/notifications/__init__.py diff --git a/ceilometer/tests/ipmi/notifications/ipmi_test_data.py b/ceilometer/tests/ipmi/notifications/ipmi_test_data.py new file mode 100644 index 00000000..ec3f455f --- /dev/null +++ b/ceilometer/tests/ipmi/notifications/ipmi_test_data.py @@ -0,0 +1,797 @@ +# +# Copyright 2014 Red Hat, Inc +# +# Author: Chris Dent <chdent@redhat.com> +# +# 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. +""" + + +TEMPERATURE_DATA = { + '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_DATA = { + '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_DATA = { + '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_DATA = { + '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' + } +} + + +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': TEMPERATURE_DATA, + 'Current': CURRENT_DATA, + 'Fan': FAN_DATA, + 'Voltage': VOLTAGE_DATA + } + } +} + + +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/ipmi/notifications/test_ironic.py index ea85278a..81eecfd6 100644 --- a/ceilometer/tests/hardware/notifications/test_ipmi.py +++ b/ceilometer/tests/ipmi/notifications/test_ironic.py @@ -20,9 +20,9 @@ import mock from oslotest import base -from ceilometer.hardware.notifications import ipmi +from ceilometer.ipmi.notifications import ironic as ipmi from ceilometer import sample -from ceilometer.tests.hardware.notifications import ipmi_test_data +from ceilometer.tests.ipmi.notifications import ipmi_test_data class TestNotifications(base.BaseTestCase): @@ -144,7 +144,7 @@ class TestNotifications(base.BaseTestCase): self.assertEqual(0, len(counters), 'expected 0 readings') - @mock.patch('ceilometer.hardware.notifications.ipmi.LOG') + @mock.patch('ceilometer.ipmi.notifications.ironic.LOG') def test_missing_sensor_data(self, mylog): processor = ipmi.TemperatureSensorNotification(None) @@ -160,7 +160,7 @@ class TestNotifications(base.BaseTestCase): messages[0] ) - @mock.patch('ceilometer.hardware.notifications.ipmi.LOG') + @mock.patch('ceilometer.ipmi.notifications.ironic.LOG') def test_sensor_data_malformed(self, mylog): processor = ipmi.TemperatureSensorNotification(None) @@ -176,7 +176,7 @@ class TestNotifications(base.BaseTestCase): messages[0] ) - @mock.patch('ceilometer.hardware.notifications.ipmi.LOG') + @mock.patch('ceilometer.ipmi.notifications.ironic.LOG') def test_missing_node_uuid(self, mylog): """Test for desired error message when 'node_uuid' missing. @@ -196,7 +196,7 @@ class TestNotifications(base.BaseTestCase): messages[0] ) - @mock.patch('ceilometer.hardware.notifications.ipmi.LOG') + @mock.patch('ceilometer.ipmi.notifications.ironic.LOG') def test_missing_sensor_id(self, mylog): """Test for desired error message when 'Sensor ID' missing.""" processor = ipmi.TemperatureSensorNotification(None) diff --git a/ceilometer/tests/ipmi/platform/__init__.py b/ceilometer/tests/ipmi/platform/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ceilometer/tests/ipmi/platform/__init__.py diff --git a/ceilometer/tests/ipmi/platform/fake_utils.py b/ceilometer/tests/ipmi/platform/fake_utils.py new file mode 100644 index 00000000..d5fe47fb --- /dev/null +++ b/ceilometer/tests/ipmi/platform/fake_utils.py @@ -0,0 +1,98 @@ +# Copyright 2014 Intel Corp. +# +# Author: Zhai Edwin <edwin.zhai@intel.com> +# +# 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. + +import binascii + +from ceilometer.ipmi.platform import exception as nmexcept +from ceilometer.ipmi.platform import intel_node_manager as node_manager +from ceilometer.tests.ipmi.platform import ipmitool_test_data as test_data + + +def get_sensor_status_init(parameter=''): + return (' 01\n', '') + + +def get_sensor_status_uninit(parameter=''): + return (' 00\n', '') + + +def init_sensor_agent(parameter=''): + return (' 00\n', '') + + +def sdr_dump(data_file=''): + if data_file == '': + raise ValueError("No file specified for ipmitool sdr dump") + fake_slave_address = '2c' + fake_channel = '60' + hexstr = node_manager.INTEL_PREFIX + fake_slave_address + fake_channel + data = binascii.unhexlify(hexstr) + with open(data_file, 'wb') as bin_fp: + bin_fp.write(data) + + return ('', '') + + +def _execute(funcs, *cmd, **kwargs): + + datas = { + test_data.device_id_cmd: test_data.device_id, + test_data.nm_device_id_cmd: test_data.nm_device_id, + test_data.get_power_cmd: test_data.power_data, + test_data.get_temperature_cmd: test_data.temperature_data, + test_data.sdr_info_cmd: test_data.sdr_info, + test_data.read_sensor_temperature_cmd: test_data.sensor_temperature, + test_data.read_sensor_voltage_cmd: test_data.sensor_voltage, + test_data.read_sensor_current_cmd: test_data.sensor_current, + test_data.read_sensor_fan_cmd: test_data.sensor_fan, + } + + if cmd[1] == 'sdr' and cmd[2] == 'dump': + # ipmitool sdr dump /tmp/XXXX + cmd_str = "".join(cmd[:3]) + par_str = cmd[3] + else: + cmd_str = "".join(cmd) + par_str = '' + + try: + return datas[cmd_str] + except KeyError: + return funcs[cmd_str](par_str) + + +def execute_with_nm(*cmd, **kwargs): + """test version of execute on Node Manager platform.""" + + funcs = {test_data.sensor_status_cmd: get_sensor_status_init, + test_data.init_sensor_cmd: init_sensor_agent, + test_data.sdr_dump_cmd: sdr_dump} + + return _execute(funcs, *cmd, **kwargs) + + +def execute_without_nm(*cmd, **kwargs): + """test version of execute on Non-Node Manager platform.""" + + funcs = {test_data.sensor_status_cmd: get_sensor_status_uninit, + test_data.init_sensor_cmd: init_sensor_agent, + test_data.sdr_dump_cmd: sdr_dump} + + return _execute(funcs, *cmd, **kwargs) + + +def execute_without_ipmi(*cmd, **kwargs): + raise nmexcept.IPMIException diff --git a/ceilometer/tests/ipmi/platform/ipmitool_test_data.py b/ceilometer/tests/ipmi/platform/ipmitool_test_data.py new file mode 100644 index 00000000..b64580e9 --- /dev/null +++ b/ceilometer/tests/ipmi/platform/ipmitool_test_data.py @@ -0,0 +1,359 @@ +# Copyright 2014 Intel Corp. +# +# Author: Zhai Edwin <edwin.zhai@intel.com> +# +# 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_intel_node_manager and test_ipmi_sensor. + +This data is provided as a sample of the data expected from the ipmitool +binary, which produce Node Manager/IPMI raw data +""" + +sensor_temperature_data = """Sensor ID : SSB Therm Trip (0xd) + Entity ID : 7.1 (System Board) + Sensor Type (Discrete): Temperature + Assertions Enabled : Digital State + [State Asserted] + Deassertions Enabled : Digital State + [State Asserted] + +Sensor ID : BB P1 VR Temp (0x20) + Entity ID : 7.1 (System Board) + Sensor Type (Analog) : Temperature + Sensor Reading : 25 (+/- 0) degrees C + Status : ok + Nominal Reading : 58.000 + Normal Minimum : 10.000 + Normal Maximum : 105.000 + Upper critical : 115.000 + Upper non-critical : 110.000 + Lower critical : 0.000 + Lower non-critical : 5.000 + Positive Hysteresis : 2.000 + Negative Hysteresis : 2.000 + Minimum sensor range : Unspecified + Maximum sensor range : Unspecified + Event Message Control : Per-threshold + Readable Thresholds : lcr lnc unc ucr + Settable Thresholds : lcr lnc unc ucr + Threshold Read Mask : lcr lnc unc ucr + Assertion Events : + Assertions Enabled : lnc- lcr- unc+ ucr+ + Deassertions Enabled : lnc- lcr- unc+ ucr+ + +Sensor ID : Front Panel Temp (0x21) + Entity ID : 12.1 (Front Panel Board) + Sensor Type (Analog) : Temperature + Sensor Reading : 23 (+/- 0) degrees C + Status : ok + Nominal Reading : 28.000 + Normal Minimum : 10.000 + Normal Maximum : 45.000 + Upper critical : 55.000 + Upper non-critical : 50.000 + Lower critical : 0.000 + Lower non-critical : 5.000 + Positive Hysteresis : 2.000 + Negative Hysteresis : 2.000 + Minimum sensor range : Unspecified + Maximum sensor range : Unspecified + Event Message Control : Per-threshold + Readable Thresholds : lcr lnc unc ucr + Settable Thresholds : lcr lnc unc ucr + Threshold Read Mask : lcr lnc unc ucr + Assertion Events : + Assertions Enabled : lnc- lcr- unc+ ucr+ + Deassertions Enabled : lnc- lcr- unc+ ucr+ + +Sensor ID : SSB Temp (0x22) + Entity ID : 7.1 (System Board) + Sensor Type (Analog) : Temperature + Sensor Reading : 43 (+/- 0) degrees C + Status : ok + Nominal Reading : 52.000 + Normal Minimum : 10.000 + Normal Maximum : 93.000 + Upper critical : 103.000 + Upper non-critical : 98.000 + Lower critical : 0.000 + Lower non-critical : 5.000 + Positive Hysteresis : 2.000 + Negative Hysteresis : 2.000 + Minimum sensor range : Unspecified + Maximum sensor range : Unspecified + Event Message Control : Per-threshold + Readable Thresholds : lcr lnc unc ucr + Settable Thresholds : lcr lnc unc ucr + Threshold Read Mask : lcr lnc unc ucr + Assertion Events : + Assertions Enabled : lnc- lcr- unc+ ucr+ + Deassertions Enabled : lnc- lcr- unc+ ucr+ + +""" + +sensor_voltage_data = """Sensor ID : VR Watchdog (0xb) + Entity ID : 7.1 (System Board) + Sensor Type (Discrete): Voltage + Assertions Enabled : Digital State + [State Asserted] + Deassertions Enabled : Digital State + [State Asserted] + +Sensor ID : BB +12.0V (0xd0) + Entity ID : 7.1 (System Board) + Sensor Type (Analog) : Voltage + Sensor Reading : 11.831 (+/- 0) Volts + Status : ok + Nominal Reading : 11.935 + Normal Minimum : 11.363 + Normal Maximum : 12.559 + Upper critical : 13.391 + Upper non-critical : 13.027 + Lower critical : 10.635 + Lower non-critical : 10.947 + Positive Hysteresis : 0.052 + Negative Hysteresis : 0.052 + Minimum sensor range : Unspecified + Maximum sensor range : Unspecified + Event Message Control : Per-threshold + Readable Thresholds : lcr lnc unc ucr + Settable Thresholds : lcr lnc unc ucr + Threshold Read Mask : lcr lnc unc ucr + Assertion Events : + Assertions Enabled : lnc- lcr- unc+ ucr+ + Deassertions Enabled : lnc- lcr- unc+ ucr+ + +Sensor ID : BB +1.35 P1LV AB (0xe4) + Entity ID : 7.1 (System Board) + Sensor Type (Analog) : Voltage + Sensor Reading : Disabled + Status : Disabled + Nominal Reading : 1.342 + Normal Minimum : 1.275 + Normal Maximum : 1.409 + Upper critical : 1.488 + Upper non-critical : 1.445 + Lower critical : 1.201 + Lower non-critical : 1.244 + Positive Hysteresis : 0.006 + Negative Hysteresis : 0.006 + Minimum sensor range : Unspecified + Maximum sensor range : Unspecified + Event Message Control : Per-threshold + Readable Thresholds : lcr lnc unc ucr + Settable Thresholds : lcr lnc unc ucr + Threshold Read Mask : lcr lnc unc ucr + Event Status : Unavailable + Assertions Enabled : lnc- lcr- unc+ ucr+ + Deassertions Enabled : lnc- lcr- unc+ ucr+ + +Sensor ID : BB +5.0V (0xd1) + Entity ID : 7.1 (System Board) + Sensor Type (Analog) : Voltage + Sensor Reading : 4.959 (+/- 0) Volts + Status : ok + Nominal Reading : 4.981 + Normal Minimum : 4.742 + Normal Maximum : 5.241 + Upper critical : 5.566 + Upper non-critical : 5.415 + Lower critical : 4.416 + Lower non-critical : 4.546 + Positive Hysteresis : 0.022 + Negative Hysteresis : 0.022 + Minimum sensor range : Unspecified + Maximum sensor range : Unspecified + Event Message Control : Per-threshold + Readable Thresholds : lcr lnc unc ucr + Settable Thresholds : lcr lnc unc ucr + Threshold Read Mask : lcr lnc unc ucr + Assertion Events : + Assertions Enabled : lnc- lcr- unc+ ucr+ + Deassertions Enabled : lnc- lcr- unc+ ucr+ + +""" + +sensor_current_data = """Sensor ID : PS1 Curr Out % (0x58) + Entity ID : 10.1 (Power Supply) + Sensor Type (Analog) : Current + Sensor Reading : 11 (+/- 0) unspecified + Status : ok + Nominal Reading : 50.000 + Normal Minimum : 0.000 + Normal Maximum : 100.000 + Upper critical : 118.000 + Upper non-critical : 100.000 + Positive Hysteresis : Unspecified + Negative Hysteresis : Unspecified + Minimum sensor range : Unspecified + Maximum sensor range : Unspecified + Event Message Control : Per-threshold + Readable Thresholds : unc ucr + Settable Thresholds : unc ucr + Threshold Read Mask : unc ucr + Assertion Events : + Assertions Enabled : unc+ ucr+ + Deassertions Enabled : unc+ ucr+ + +Sensor ID : PS2 Curr Out % (0x59) + Entity ID : 10.2 (Power Supply) + Sensor Type (Analog) : Current + Sensor Reading : 0 (+/- 0) unspecified + Status : ok + Nominal Reading : 50.000 + Normal Minimum : 0.000 + Normal Maximum : 100.000 + Upper critical : 118.000 + Upper non-critical : 100.000 + Positive Hysteresis : Unspecified + Negative Hysteresis : Unspecified + Minimum sensor range : Unspecified + Maximum sensor range : Unspecified + Event Message Control : Per-threshold + Readable Thresholds : unc ucr + Settable Thresholds : unc ucr + Threshold Read Mask : unc ucr + Assertion Events : + Assertions Enabled : unc+ ucr+ + Deassertions Enabled : unc+ ucr+ + +""" + +sensor_fan_data = """Sensor ID : System Fan 1 (0x30) + Entity ID : 29.1 (Fan Device) + Sensor Type (Analog) : Fan + Sensor Reading : 4704 (+/- 0) RPM + Status : ok + Nominal Reading : 7497.000 + Normal Minimum : 2499.000 + Normal Maximum : 12495.000 + Lower critical : 1715.000 + Lower non-critical : 1960.000 + Positive Hysteresis : 49.000 + Negative Hysteresis : 49.000 + Minimum sensor range : Unspecified + Maximum sensor range : Unspecified + Event Message Control : Per-threshold + Readable Thresholds : lcr lnc + Settable Thresholds : lcr lnc + Threshold Read Mask : lcr lnc + Assertion Events : + Assertions Enabled : lnc- lcr- + Deassertions Enabled : lnc- lcr- + +Sensor ID : System Fan 2 (0x32) + Entity ID : 29.2 (Fan Device) + Sensor Type (Analog) : Fan + Sensor Reading : 4704 (+/- 0) RPM + Status : ok + Nominal Reading : 7497.000 + Normal Minimum : 2499.000 + Normal Maximum : 12495.000 + Lower critical : 1715.000 + Lower non-critical : 1960.000 + Positive Hysteresis : 49.000 + Negative Hysteresis : 49.000 + Minimum sensor range : Unspecified + Maximum sensor range : Unspecified + Event Message Control : Per-threshold + Readable Thresholds : lcr lnc + Settable Thresholds : lcr lnc + Threshold Read Mask : lcr lnc + Assertion Events : + Assertions Enabled : lnc- lcr- + Deassertions Enabled : lnc- lcr- + +Sensor ID : System Fan 3 (0x34) + Entity ID : 29.3 (Fan Device) + Sensor Type (Analog) : Fan + Sensor Reading : 4704 (+/- 0) RPM + Status : ok + Nominal Reading : 7497.000 + Normal Minimum : 2499.000 + Normal Maximum : 12495.000 + Lower critical : 1715.000 + Lower non-critical : 1960.000 + Positive Hysteresis : 49.000 + Negative Hysteresis : 49.000 + Minimum sensor range : Unspecified + Maximum sensor range : Unspecified + Event Message Control : Per-threshold + Readable Thresholds : lcr lnc + Settable Thresholds : lcr lnc + Threshold Read Mask : lcr lnc + Assertion Events : + Assertions Enabled : lnc- lcr- + Deassertions Enabled : lnc- lcr- + +Sensor ID : System Fan 4 (0x36) + Entity ID : 29.4 (Fan Device) + Sensor Type (Analog) : Fan + Sensor Reading : 4606 (+/- 0) RPM + Status : ok + Nominal Reading : 7497.000 + Normal Minimum : 2499.000 + Normal Maximum : 12495.000 + Lower critical : 1715.000 + Lower non-critical : 1960.000 + Positive Hysteresis : 49.000 + Negative Hysteresis : 49.000 + Minimum sensor range : Unspecified + Maximum sensor range : Unspecified + Event Message Control : Per-threshold + Readable Thresholds : lcr lnc + Settable Thresholds : lcr lnc + Threshold Read Mask : lcr lnc + Assertion Events : + Assertions Enabled : lnc- lcr- + Deassertions Enabled : lnc- lcr- + +""" + + +sensor_status_cmd = 'ipmitoolraw0x0a0x2c0x00' +init_sensor_cmd = 'ipmitoolraw0x0a0x2c0x01' +sdr_dump_cmd = 'ipmitoolsdrdump' +sdr_info_cmd = 'ipmitoolsdrinfo' + +read_sensor_all_cmd = 'ipmitoolsdr-v' +read_sensor_temperature_cmd = 'ipmitoolsdr-vtypeTemperature' +read_sensor_voltage_cmd = 'ipmitoolsdr-vtypeVoltage' +read_sensor_current_cmd = 'ipmitoolsdr-vtypeCurrent' +read_sensor_fan_cmd = 'ipmitoolsdr-vtypeFan' + +device_id_cmd = 'ipmitoolraw0x060x01' +nm_device_id_cmd = 'ipmitool-b0x6-t0x2craw0x060x01' +get_power_cmd = 'ipmitool-b0x6-t0x2craw0x2e0xc80x570x010x000x010x000x00' +get_temperature_cmd = 'ipmitool-b0x6-t0x2craw0x2e0xc80x570x010x000x020x000x00' + + +device_id = (' 21 01 01 04 02 bf 57 01 00 49 00 01 07 50 0b', '') +nm_device_id = (' 50 01 02 15 02 21 57 01 00 02 0b 02 09 10 01', '') + +# start from byte 3, get cur- 57 00(87), min- 03 00(3) +# max- 37 02(567), avg- 5c 00(92) +power_data = (' 57 01 00 57 00 03 00 37 02 5c 00 cc 37 f4 53 ce\n' + ' 9b 12 01 50\n', '') + +# start from byte 3, get cur- 17 00(23), min- 16 00(22) +# max- 18 00(24), avg- 17 00(23) +temperature_data = (' 57 01 00 17 00 16 00 18 00 17 00 f3 6f fe 53 85\n' + ' b7 02 00 50\n', '') + +sdr_info = ('', '') + +sensor_temperature = (sensor_temperature_data, '') +sensor_voltage = (sensor_voltage_data, '') +sensor_current = (sensor_current_data, '') +sensor_fan = (sensor_fan_data, '') diff --git a/ceilometer/tests/ipmi/platform/test_intel_node_manager.py b/ceilometer/tests/ipmi/platform/test_intel_node_manager.py new file mode 100644 index 00000000..e795332d --- /dev/null +++ b/ceilometer/tests/ipmi/platform/test_intel_node_manager.py @@ -0,0 +1,84 @@ +# Copyright 2014 Intel Corp. +# +# Author: Zhai Edwin <edwin.zhai@intel.com> +# +# 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. + +import mock + +from ceilometer.ipmi.platform import intel_node_manager as node_manager +from ceilometer.tests.ipmi.platform import fake_utils +from ceilometer import utils + +from oslotest import base + + +class TestNodeManager(base.BaseTestCase): + + def setUp(self): + super(TestNodeManager, self).setUp() + + utils.execute = mock.Mock(side_effect=fake_utils.execute_with_nm) + self.nm = node_manager.NodeManager() + + def test_read_power_all(self): + power = self.nm.read_power_all() + + avg_val = node_manager._hex(power["Average_value"]) + max_val = node_manager._hex(power["Maximum_value"]) + min_val = node_manager._hex(power["Minimum_value"]) + cur_val = node_manager._hex(power["Current_value"]) + + self.assertTrue(self.nm.nm_support) + # see ipmi_test_data.py for raw data + self.assertEqual(87, cur_val) + self.assertEqual(3, min_val) + self.assertEqual(567, max_val) + self.assertEqual(92, avg_val) + + def test_read_temperature_all(self): + temperature = self.nm.read_temperature_all() + + avg_val = node_manager._hex(temperature["Average_value"]) + max_val = node_manager._hex(temperature["Maximum_value"]) + min_val = node_manager._hex(temperature["Minimum_value"]) + cur_val = node_manager._hex(temperature["Current_value"]) + + self.assertTrue(self.nm.nm_support) + # see ipmi_test_data.py for raw data + self.assertEqual(23, cur_val) + self.assertEqual(22, min_val) + self.assertEqual(24, max_val) + self.assertEqual(23, avg_val) + + +class TestNonNodeManager(base.BaseTestCase): + + def setUp(self): + super(TestNonNodeManager, self).setUp() + + utils.execute = mock.Mock(side_effect=fake_utils.execute_without_nm) + self.nm = node_manager.NodeManager() + self.nm.nm_support = False + + def test_read_power_all(self): + power = self.nm.read_power_all() + + # Non-Node Manager platform return empty data + self.assertTrue(power == {}) + + def test_read_temperature_all(self): + temperature = self.nm.read_temperature_all() + + # Non-Node Manager platform return empty data + self.assertTrue(temperature == {}) diff --git a/ceilometer/tests/ipmi/platform/test_ipmi_sensor.py b/ceilometer/tests/ipmi/platform/test_ipmi_sensor.py new file mode 100644 index 00000000..3d5deca5 --- /dev/null +++ b/ceilometer/tests/ipmi/platform/test_ipmi_sensor.py @@ -0,0 +1,118 @@ +# Copyright 2014 Intel Corp. +# +# Author: Zhai Edwin <edwin.zhai@intel.com> +# +# 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. + +import mock + +from ceilometer.ipmi.platform import ipmi_sensor +from ceilometer.tests.ipmi.platform import fake_utils +from ceilometer import utils + +from oslotest import base + + +class TestIPMISensor(base.BaseTestCase): + + def setUp(self): + super(TestIPMISensor, self).setUp() + + utils.execute = mock.Mock(side_effect=fake_utils.execute_with_nm) + self.ipmi = ipmi_sensor.IPMISensor() + + def test_read_sensor_temperature(self): + sensors = self.ipmi.read_sensor_any('Temperature') + + # only temperature data returned. + self.assertTrue('Temperature' in sensors) + self.assertEqual(1, len(sensors)) + + # 4 sensor data in total, ignore 1 without 'Sensor Reading'. + # Check ceilometer/tests/ipmi/platform/ipmi_test_data.py + self.assertEqual(3, len(sensors['Temperature'])) + sensor = sensors['Temperature']['BB P1 VR Temp (0x20)'] + self.assertEqual('25 (+/- 0) degrees C', sensor['Sensor Reading']) + + def test_read_sensor_voltage(self): + sensors = self.ipmi.read_sensor_any('Voltage') + + # only voltage data returned. + self.assertTrue('Voltage' in sensors) + self.assertEqual(1, len(sensors)) + + # 4 sensor data in total, ignore 1 without 'Sensor Reading'. + # Check ceilometer/tests/ipmi/platform/ipmi_test_data.py + self.assertEqual(3, len(sensors['Voltage'])) + sensor = sensors['Voltage']['BB +5.0V (0xd1)'] + self.assertEqual('4.959 (+/- 0) Volts', sensor['Sensor Reading']) + + def test_read_sensor_current(self): + sensors = self.ipmi.read_sensor_any('Current') + + # only Current data returned. + self.assertTrue('Current' in sensors) + self.assertEqual(1, len(sensors)) + + # 2 sensor data in total. + # Check ceilometer/tests/ipmi/platform/ipmi_test_data.py + self.assertEqual(2, len(sensors['Current'])) + sensor = sensors['Current']['PS1 Curr Out % (0x58)'] + self.assertEqual('11 (+/- 0) unspecified', sensor['Sensor Reading']) + + def test_read_sensor_fan(self): + sensors = self.ipmi.read_sensor_any('Fan') + + # only Fan data returned. + self.assertTrue('Fan' in sensors) + self.assertEqual(1, len(sensors)) + + # 2 sensor data in total. + # Check ceilometer/tests/ipmi/platform/ipmi_test_data.py + self.assertEqual(4, len(sensors['Fan'])) + sensor = sensors['Fan']['System Fan 2 (0x32)'] + self.assertEqual('4704 (+/- 0) RPM', sensor['Sensor Reading']) + + +class TestNonIPMISensor(base.BaseTestCase): + + def setUp(self): + super(TestNonIPMISensor, self).setUp() + + utils.execute = mock.Mock(side_effect=fake_utils.execute_without_ipmi) + self.ipmi = ipmi_sensor.IPMISensor() + self.ipmi.ipmi_support = False + + def test_read_sensor_temperature(self): + sensors = self.ipmi.read_sensor_any('Temperature') + + # Non-IPMI platform return empty data + self.assertTrue(sensors == {}) + + def test_read_sensor_voltage(self): + sensors = self.ipmi.read_sensor_any('Voltage') + + # Non-IPMI platform return empty data + self.assertTrue(sensors == {}) + + def test_read_sensor_current(self): + sensors = self.ipmi.read_sensor_any('Current') + + # Non-IPMI platform return empty data + self.assertTrue(sensors == {}) + + def test_read_sensor_fan(self): + sensors = self.ipmi.read_sensor_any('Fan') + + # Non-IPMI platform return empty data + self.assertTrue(sensors == {}) diff --git a/ceilometer/tests/ipmi/pollsters/__init__.py b/ceilometer/tests/ipmi/pollsters/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ceilometer/tests/ipmi/pollsters/__init__.py diff --git a/ceilometer/tests/ipmi/pollsters/base.py b/ceilometer/tests/ipmi/pollsters/base.py new file mode 100644 index 00000000..76d0e260 --- /dev/null +++ b/ceilometer/tests/ipmi/pollsters/base.py @@ -0,0 +1,67 @@ +# Copyright 2014 Intel +# +# Author: Zhai Edwin <edwin.zhai@intel.com> +# +# 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. + +import abc +import mock +import six + +from ceilometer.ipmi import manager +import ceilometer.tests.base as base + +from oslotest import mockpatch + + +@six.add_metaclass(abc.ABCMeta) +class TestPollsterBase(base.BaseTestCase): + + @abc.abstractmethod + def fake_data(self): + """Fake data used for test.""" + + @abc.abstractmethod + def fake_sensor_data(self, sensor_type): + """Fake sensor data used for test.""" + + @abc.abstractmethod + def make_pollster(self): + """Produce right pollster for test.""" + + def _test_get_samples(self): + nm = mock.Mock() + nm.read_temperature_all.side_effect = self.fake_data + nm.read_power_all.side_effect = self.fake_data + nm.read_sensor_any.side_effect = self.fake_sensor_data + + self.mgr = manager.AgentManager() + + self.useFixture(mockpatch.Patch( + 'ceilometer.ipmi.platform.intel_node_manager.NodeManager', + return_value=nm)) + + self.useFixture(mockpatch.Patch( + 'ceilometer.ipmi.platform.ipmi_sensor.IPMISensor', + return_value=nm)) + + self.pollster = self.make_pollster() + + def _verify_metering(self, length, expected_vol): + cache = {} + resources = {} + + samples = list(self.pollster.get_samples(self.mgr, cache, resources)) + self.assertEqual(length, len(samples)) + + self.assertTrue(any(s.volume == expected_vol for s in samples)) diff --git a/ceilometer/tests/ipmi/pollsters/test_node.py b/ceilometer/tests/ipmi/pollsters/test_node.py new file mode 100644 index 00000000..296e1a12 --- /dev/null +++ b/ceilometer/tests/ipmi/pollsters/test_node.py @@ -0,0 +1,62 @@ +# Copyright 2014 Intel Corp. +# +# Author: Zhai Edwin <edwin.zhai@intel.com> +# +# 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. + +import mock + +from ceilometer.ipmi.pollsters import node +from ceilometer.tests.ipmi.pollsters import base + + +class TestPowerPollster(base.TestPollsterBase): + + def fake_data(self): + # data after parsing Intel Node Manager output + return {"Current_value": ['13', '00']} + + def fake_sensor_data(self, sensor_type): + # No use for this test + return None + + def make_pollster(self): + return node.PowerPollster() + + @mock.patch('ceilometer.pipeline.setup_pipeline', mock.MagicMock()) + def test_get_samples(self): + self._test_get_samples() + + # only one sample, and value is 19(0x13 as current_value) + self._verify_metering(1, 19) + + +class TestTemperaturePollster(base.TestPollsterBase): + + def fake_data(self): + # data after parsing Intel Node Manager output + return {"Current_value": ['23', '00']} + + def fake_sensor_data(self, sensor_type): + # No use for this test + return None + + def make_pollster(self): + return node.TemperaturePollster() + + @mock.patch('ceilometer.pipeline.setup_pipeline', mock.MagicMock()) + def test_get_samples(self): + self._test_get_samples() + + # only one sample, and value is 35(0x23 as current_value) + self._verify_metering(1, 35) diff --git a/ceilometer/tests/ipmi/pollsters/test_sensor.py b/ceilometer/tests/ipmi/pollsters/test_sensor.py new file mode 100644 index 00000000..eb7c9a7f --- /dev/null +++ b/ceilometer/tests/ipmi/pollsters/test_sensor.py @@ -0,0 +1,114 @@ +# Copyright 2014 Intel Corp. +# +# Author: Zhai Edwin <edwin.zhai@intel.com> +# +# 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. + +import mock + +from ceilometer.ipmi.pollsters import sensor +from ceilometer.tests.ipmi.notifications import ipmi_test_data +from ceilometer.tests.ipmi.pollsters import base + + +TEMPERATURE_SENSOR_DATA = { + 'Temperature': ipmi_test_data.TEMPERATURE_DATA +} + +CURRENT_SENSOR_DATA = { + 'Current': ipmi_test_data.CURRENT_DATA +} + +FAN_SENSOR_DATA = { + 'Fan': ipmi_test_data.FAN_DATA +} + +VOLTAGE_SENSOR_DATA = { + 'Voltage': ipmi_test_data.VOLTAGE_DATA +} + + +class TestTemperatureSensorPollster(base.TestPollsterBase): + + def fake_sensor_data(self, sensor_type): + return TEMPERATURE_SENSOR_DATA + + def fake_data(self): + # No use for Sensor test + return None + + def make_pollster(self): + return sensor.TemperatureSensorPollster() + + @mock.patch('ceilometer.pipeline.setup_pipeline', mock.MagicMock()) + def test_get_samples(self): + self._test_get_samples() + + self._verify_metering(10, float(32)) + + +class TestFanSensorPollster(base.TestPollsterBase): + + def fake_sensor_data(self, sensor_type): + return FAN_SENSOR_DATA + + def fake_data(self): + # No use for Sensor test + return None + + def make_pollster(self): + return sensor.FanSensorPollster() + + @mock.patch('ceilometer.pipeline.setup_pipeline', mock.MagicMock()) + def test_get_samples(self): + self._test_get_samples() + + self._verify_metering(12, float(7140)) + + +class TestCurrentSensorPollster(base.TestPollsterBase): + + def fake_sensor_data(self, sensor_type): + return CURRENT_SENSOR_DATA + + def fake_data(self): + # No use for Sensor test + return None + + def make_pollster(self): + return sensor.CurrentSensorPollster() + + @mock.patch('ceilometer.pipeline.setup_pipeline', mock.MagicMock()) + def test_get_samples(self): + self._test_get_samples() + + self._verify_metering(1, float(130)) + + +class TestVoltageSensorPollster(base.TestPollsterBase): + + def fake_sensor_data(self, sensor_type): + return VOLTAGE_SENSOR_DATA + + def fake_data(self): + # No use for Sensor test + return None + + def make_pollster(self): + return sensor.VoltageSensorPollster() + + @mock.patch('ceilometer.pipeline.setup_pipeline', mock.MagicMock()) + def test_get_samples(self): + self._test_get_samples() + + self._verify_metering(4, float(3.309)) diff --git a/ceilometer/tests/ipmi/test_manager.py b/ceilometer/tests/ipmi/test_manager.py new file mode 100644 index 00000000..795fb0eb --- /dev/null +++ b/ceilometer/tests/ipmi/test_manager.py @@ -0,0 +1,42 @@ +# Copyright 2014 Intel Corp. +# +# Author: Zhai Edwin <edwin.zhai@intel.com> +# +# 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 ceilometer/ipmi/manager.py +""" + +from ceilometer.ipmi import manager +from ceilometer.tests import agentbase + +import mock +from oslotest import base + + +class TestManager(base.BaseTestCase): + + @mock.patch('ceilometer.pipeline.setup_pipeline', mock.MagicMock()) + def test_load_plugins(self): + mgr = manager.AgentManager() + self.assertIsNotNone(list(mgr.pollster_manager)) + + +class TestRunTasks(agentbase.BaseAgentManagerTestCase): + + @staticmethod + def create_manager(): + return manager.AgentManager() + + def setUp(self): + self.source_resources = True + super(TestRunTasks, self).setUp() diff --git a/ceilometer/tests/network/services/test_fwaas.py b/ceilometer/tests/network/services/test_fwaas.py index ee2ad86f..f59743d5 100644 --- a/ceilometer/tests/network/services/test_fwaas.py +++ b/ceilometer/tests/network/services/test_fwaas.py @@ -106,7 +106,7 @@ class TestFirewallPollster(_BaseTestFWPollster): set([s.name for s in samples])) def test_vpn_discovery(self): - discovered_fws = discovery.FirewallDiscovery().discover() + discovered_fws = discovery.FirewallDiscovery().discover(self.manager) self.assertEqual(3, len(discovered_fws)) for vpn in self.fake_get_fw_service(): @@ -165,6 +165,7 @@ class TestIPSecConnectionsPollster(_BaseTestFWPollster): set([s.name for s in samples])) def test_fw_policy_discovery(self): - discovered_policy = discovery.FirewallPolicyDiscovery().discover() + discovered_policy = discovery.FirewallPolicyDiscovery().discover( + self.manager) self.assertEqual(1, len(discovered_policy)) self.assertEqual(self.fake_get_fw_policy(), discovered_policy) diff --git a/ceilometer/tests/network/services/test_lbaas.py b/ceilometer/tests/network/services/test_lbaas.py index 60322e56..ed37c348 100644 --- a/ceilometer/tests/network/services/test_lbaas.py +++ b/ceilometer/tests/network/services/test_lbaas.py @@ -153,7 +153,7 @@ class TestLBPoolPollster(_BaseTestLBPollster): set([s.name for s in samples])) def test_pool_discovery(self): - discovered_pools = discovery.LBPoolsDiscovery().discover() + discovered_pools = discovery.LBPoolsDiscovery().discover(self.manager) self.assertEqual(4, len(discovered_pools)) for pool in self.fake_get_pools(): if pool['status'] == 'error': @@ -276,7 +276,7 @@ class TestLBVipPollster(_BaseTestLBPollster): set([s.name for s in samples])) def test_vip_discovery(self): - discovered_vips = discovery.LBVipsDiscovery().discover() + discovered_vips = discovery.LBVipsDiscovery().discover(self.manager) self.assertEqual(4, len(discovered_vips)) for pool in self.fake_get_vips(): if pool['status'] == 'error': @@ -369,7 +369,8 @@ class TestLBMemberPollster(_BaseTestLBPollster): set([s.name for s in samples])) def test_members_discovery(self): - discovered_members = discovery.LBMembersDiscovery().discover() + discovered_members = discovery.LBMembersDiscovery().discover( + self.manager) self.assertEqual(4, len(discovered_members)) for pool in self.fake_get_members(): if pool['status'] == 'error': @@ -417,7 +418,8 @@ class TestLBHealthProbePollster(_BaseTestLBPollster): set([s.name for s in samples])) def test_probes_discovery(self): - discovered_probes = discovery.LBHealthMonitorsDiscovery().discover() + discovered_probes = discovery.LBHealthMonitorsDiscovery().discover( + self.manager) self.assertEqual(discovered_probes, self.fake_get_health_monitor()) diff --git a/ceilometer/tests/network/services/test_vpnaas.py b/ceilometer/tests/network/services/test_vpnaas.py index 228da739..864a2efb 100644 --- a/ceilometer/tests/network/services/test_vpnaas.py +++ b/ceilometer/tests/network/services/test_vpnaas.py @@ -110,7 +110,8 @@ class TestVPNServicesPollster(_BaseTestVPNPollster): set([s.name for s in samples])) def test_vpn_discovery(self): - discovered_vpns = discovery.VPNServicesDiscovery().discover() + discovered_vpns = discovery.VPNServicesDiscovery().discover( + self.manager) self.assertEqual(3, len(discovered_vpns)) for vpn in self.fake_get_vpn_service(): @@ -170,6 +171,7 @@ class TestIPSecConnectionsPollster(_BaseTestVPNPollster): set([s.name for s in samples])) def test_conns_discovery(self): - discovered_conns = discovery.IPSecConnectionsDiscovery().discover() + discovered_conns = discovery.IPSecConnectionsDiscovery().discover( + self.manager) self.assertEqual(1, len(discovered_conns)) self.assertEqual(self.fake_get_ipsec_connections(), discovered_conns) diff --git a/ceilometer/utils.py b/ceilometer/utils.py index 5f05c3a0..c703521b 100644 --- a/ceilometer/utils.py +++ b/ceilometer/utils.py @@ -27,11 +27,32 @@ import hashlib import multiprocessing import struct +from ceilometer.openstack.common import processutils +from oslo.config import cfg from oslo.utils import timeutils from oslo.utils import units import six +rootwrap_conf = cfg.StrOpt('rootwrap_config', + default="/etc/ceilometer/rootwrap.conf", + help='Path to the rootwrap configuration file to' + 'use for running commands as root') +CONF = cfg.CONF +CONF.register_opt(rootwrap_conf) + + +def _get_root_helper(): + return 'sudo ceilometer-rootwrap %s' % CONF.rootwrap_config + + +def execute(*cmd, **kwargs): + """Convenience wrapper around oslo's execute() method.""" + if 'run_as_root' in kwargs and 'root_helper' not in kwargs: + kwargs['root_helper'] = _get_root_helper() + return processutils.execute(*cmd, **kwargs) + + def recursive_keypairs(d, separator=':'): """Generator that produces sequence of keypairs for nested dictionaries.""" for name, value in sorted(six.iteritems(d)): diff --git a/doc/source/conf.py b/doc/source/conf.py index cb42bef6..eb0eebaf 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -143,7 +143,6 @@ write_autodoc_index() # or your custom ones. extensions = [ 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', 'sphinxcontrib.autohttp.flask', 'wsmeext.sphinxext', 'sphinx.ext.coverage', diff --git a/doc/source/measurements.rst b/doc/source/measurements.rst index 1b5eb7da..c8ff3dad 100644 --- a/doc/source/measurements.rst +++ b/doc/source/measurements.rst @@ -194,6 +194,32 @@ snapshot.size Gauge GB snap ID notification Size of Make sure Cinder is properly configured first: see :ref:`installing_manually`. +Identity (Keystone) +=================== + +================================ ========== =============== ========== ============ =========================================== +Name Type Unit Resource Origin Note +================================ ========== =============== ========== ============ =========================================== +identity.authenticate.success Delta user user ID notification User successfully authenticates +identity.authenticate.pending Delta user user ID notification User pending authentication +identity.authenticate.failure Delta user user ID notification User failed authentication +identity.user.created Delta user user ID notification A user is created +identity.user.deleted Delta user user ID notification A user is deleted +identity.user.updated Delta user user ID notification A user is updated +identity.group.created Delta group group ID notification A group is created +identity.group.deleted Delta group group ID notification A group is deleted +identity.group.updated Delta group group ID notification A group is updated +identity.role.created Delta role role ID notification A role is created +identity.role.deleted Delta role role ID notification A role is deleted +identity.role.updated Delta role role ID notification A role is updated +identity.project.created Delta project project ID notification A project is created +identity.project.deleted Delta project project ID notification A project is deleted +identity.project.updated Delta project project ID notification A project is updated +identity.trust.created Delta trust trust ID notification A trust is created +identity.trust.deleted Delta trust trust ID notification A trust is deleted +================================ ========== =============== ========== ============ =========================================== + + Object Storage (Swift) ====================== @@ -342,6 +368,21 @@ hardware.ipmi.current Gauge W current sensor notification hardware.ipmi.voltage Gauge V voltage sensor notification Sensor Voltage Reading ============================= ========== ====== ============== ============ ========================== +There is another way to retrieve IPMI data, by deploying the Ceilometer IPMI +agent on each IPMI-capable node in order to poll local sensor data. To avoid +duplication of metering data and unnecessary load on the IPMI interface, the +IPMI agent should not be deployed if the node is managed by Ironic and the +'conductor.send_sensor_data' option is set to true in the Ironic configuration. + +IPMI agent also retrieve following Node Manager meter besides original IPMI +sensor data: + +=============================== ========== ====== ============== ============ ========================== +Meter Type Unit Resource Origin Note +=============================== ========== ====== ============== ============ ========================== +hardware.ipmi.node.power Gauge W host ID pollster System Current Power +hardware.ipmi.node.temperature Gauge C host ID pollster System Current Temperature +=============================== ========== ====== ============== ============ ========================== Dynamically retrieving the Meters via ceilometer client ======================================================= diff --git a/etc/ceilometer/rootwrap.conf b/etc/ceilometer/rootwrap.conf new file mode 100644 index 00000000..c79065c7 --- /dev/null +++ b/etc/ceilometer/rootwrap.conf @@ -0,0 +1,27 @@ +# Configuration for ceilometer-rootwrap +# This file should be owned by (and only-writeable by) the root user + +[DEFAULT] +# List of directories to load filter definitions from (separated by ','). +# These directories MUST all be only writeable by root ! +filters_path=/etc/ceilometer/rootwrap.d,/usr/share/ceilometer/rootwrap + +# List of directories to search executables in, in case filters do not +# explicitely specify a full path (separated by ',') +# If not specified, defaults to system PATH environment variable. +# These directories MUST all be only writeable by root ! +exec_dirs=/sbin,/usr/sbin,/bin,/usr/bin + +# Enable logging to syslog +# Default value is False +use_syslog=False + +# Which syslog facility to use. +# Valid values include auth, authpriv, syslog, user0, user1... +# Default value is 'syslog' +syslog_log_facility=syslog + +# Which messages to log. +# INFO means log all usage +# ERROR means only log unsuccessful attempts +syslog_log_level=ERROR diff --git a/etc/ceilometer/rootwrap.d/ipmi.filters b/etc/ceilometer/rootwrap.d/ipmi.filters new file mode 100644 index 00000000..2ef74b04 --- /dev/null +++ b/etc/ceilometer/rootwrap.d/ipmi.filters @@ -0,0 +1,7 @@ +# ceilometer-rootwrap command filters for IPMI capable nodes +# This file should be owned by (and only-writeable by) the root user + +[Filters] +# ceilometer/ipmi/nodemanager/node_manager.py: 'ipmitool' +ipmitool: CommandFilter, ipmitool, root + diff --git a/openstack-common.conf b/openstack-common.conf index 0cc63dd1..b6e337b2 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -10,6 +10,7 @@ module=log module=log_handler module=loopingcall module=policy +module=processutils module=service module=threadgroup diff --git a/requirements.txt b/requirements.txt index 5d75a5e1..1663de1f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,8 +16,10 @@ lockfile>=0.8 lxml>=2.3 msgpack-python>=0.4.0 netaddr>=0.7.6 +ordereddict oslo.db>=0.4.0 oslo.config>=1.4.0.0a3 +oslo.rootwrap>=1.3.0.0a1 oslo.vmware>=0.5 # Apache-2.0 PasteDeploy>=1.5.0 pbr>=0.6,!=0.7,<1.0 @@ -73,10 +73,10 @@ ceilometer.notification = stack_crud = ceilometer.orchestration.notifications:StackCRUD data_processing = ceilometer.data_processing.notifications:DataProcessing 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 + hardware.ipmi.temperature = ceilometer.ipmi.notifications.ironic:TemperatureSensorNotification + hardware.ipmi.voltage = ceilometer.ipmi.notifications.ironic:VoltageSensorNotification + hardware.ipmi.current = ceilometer.ipmi.notifications.ironic:CurrentSensorNotification + hardware.ipmi.fan = ceilometer.ipmi.notifications.ironic:FanSensorNotification ceilometer.discover = local_instances = ceilometer.compute.discovery:InstanceDiscovery @@ -120,6 +120,14 @@ ceilometer.poll.compute = instance_flavor = ceilometer.compute.pollsters.instance:InstanceFlavorPollster memory.usage = ceilometer.compute.pollsters.memory:MemoryUsagePollster +ceilometer.poll.ipmi = + hardware.ipmi.node.power = ceilometer.ipmi.pollsters.node:PowerPollster + hardware.ipmi.node.temperature = ceilometer.ipmi.pollsters.node:TemperaturePollster + hardware.ipmi.temperature = ceilometer.ipmi.pollsters.sensor:TemperatureSensorPollster + hardware.ipmi.voltage = ceilometer.ipmi.pollsters.sensor:VoltageSensorPollster + hardware.ipmi.current = ceilometer.ipmi.pollsters.sensor:CurrentSensorPollster + hardware.ipmi.fan = ceilometer.ipmi.pollsters.sensor:FanSensorPollster + ceilometer.poll.central = ip.floating = ceilometer.network.floatingip:FloatingIPPollster image = ceilometer.image.glance:ImagePollster @@ -260,9 +268,11 @@ console_scripts = ceilometer-agent-central = ceilometer.cmd.agent_central:main ceilometer-agent-compute = ceilometer.cmd.agent_compute:main ceilometer-agent-notification = ceilometer.cmd.agent_notification:main + ceilometer-agent-ipmi = ceilometer.cmd.agent_ipmi:main ceilometer-send-sample = ceilometer.cli:send_sample ceilometer-dbsync = ceilometer.cmd.storage:dbsync ceilometer-expirer = ceilometer.cmd.storage:expirer + ceilometer-rootwrap = oslo.rootwrap.cmd:main ceilometer-collector = ceilometer.cmd.collector:main ceilometer-alarm-evaluator = ceilometer.cmd.alarm:evaluator ceilometer-alarm-notifier = ceilometer.cmd.alarm:notifier |