summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ceilometer/agent.py2
-rw-r--r--ceilometer/alarm/service.py6
-rw-r--r--ceilometer/api/controllers/v2.py38
-rw-r--r--ceilometer/central/discovery.py16
-rw-r--r--ceilometer/cmd/agent_ipmi.py23
-rw-r--r--ceilometer/compute/discovery.py2
-rw-r--r--ceilometer/hardware/discovery.py2
-rw-r--r--ceilometer/ipmi/__init__.py (renamed from ceilometer/hardware/notifications/__init__.py)0
-rw-r--r--ceilometer/ipmi/manager.py23
-rw-r--r--ceilometer/ipmi/notifications/__init__.py (renamed from ceilometer/tests/hardware/notifications/__init__.py)0
-rw-r--r--ceilometer/ipmi/notifications/ironic.py (renamed from ceilometer/hardware/notifications/ipmi.py)44
-rw-r--r--ceilometer/ipmi/platform/__init__.py0
-rw-r--r--ceilometer/ipmi/platform/exception.py24
-rw-r--r--ceilometer/ipmi/platform/intel_node_manager.py274
-rw-r--r--ceilometer/ipmi/platform/ipmi_sensor.py115
-rw-r--r--ceilometer/ipmi/platform/ipmitool.py132
-rw-r--r--ceilometer/ipmi/pollsters/__init__.py0
-rw-r--r--ceilometer/ipmi/pollsters/node.py77
-rw-r--r--ceilometer/ipmi/pollsters/sensor.py97
-rw-r--r--ceilometer/network/services/discovery.py16
-rw-r--r--ceilometer/objectstore/swift_middleware.py10
-rw-r--r--ceilometer/openstack/common/jsonutils.py8
-rw-r--r--ceilometer/openstack/common/processutils.py285
-rw-r--r--ceilometer/openstack/common/strutils.py44
-rw-r--r--ceilometer/plugin.py3
-rw-r--r--ceilometer/storage/hbase/utils.py36
-rw-r--r--ceilometer/storage/impl_hbase.py2
-rw-r--r--ceilometer/tests/agentbase.py4
-rw-r--r--ceilometer/tests/api/v2/test_alarm_scenarios.py96
-rw-r--r--ceilometer/tests/api/v2/test_complex_query_scenarios.py29
-rw-r--r--ceilometer/tests/central/test_discovery.py46
-rw-r--r--ceilometer/tests/hardware/notifications/ipmi_test_data.py785
-rw-r--r--ceilometer/tests/ipmi/__init__.py0
-rw-r--r--ceilometer/tests/ipmi/notifications/__init__.py0
-rw-r--r--ceilometer/tests/ipmi/notifications/ipmi_test_data.py797
-rw-r--r--ceilometer/tests/ipmi/notifications/test_ironic.py (renamed from ceilometer/tests/hardware/notifications/test_ipmi.py)12
-rw-r--r--ceilometer/tests/ipmi/platform/__init__.py0
-rw-r--r--ceilometer/tests/ipmi/platform/fake_utils.py98
-rw-r--r--ceilometer/tests/ipmi/platform/ipmitool_test_data.py359
-rw-r--r--ceilometer/tests/ipmi/platform/test_intel_node_manager.py84
-rw-r--r--ceilometer/tests/ipmi/platform/test_ipmi_sensor.py118
-rw-r--r--ceilometer/tests/ipmi/pollsters/__init__.py0
-rw-r--r--ceilometer/tests/ipmi/pollsters/base.py67
-rw-r--r--ceilometer/tests/ipmi/pollsters/test_node.py62
-rw-r--r--ceilometer/tests/ipmi/pollsters/test_sensor.py114
-rw-r--r--ceilometer/tests/ipmi/test_manager.py42
-rw-r--r--ceilometer/tests/network/services/test_fwaas.py5
-rw-r--r--ceilometer/tests/network/services/test_lbaas.py10
-rw-r--r--ceilometer/tests/network/services/test_vpnaas.py6
-rw-r--r--ceilometer/utils.py21
-rw-r--r--doc/source/conf.py1
-rw-r--r--doc/source/measurements.rst41
-rw-r--r--etc/ceilometer/rootwrap.conf27
-rw-r--r--etc/ceilometer/rootwrap.d/ipmi.filters7
-rw-r--r--openstack-common.conf1
-rw-r--r--requirements.txt2
-rw-r--r--setup.cfg18
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
diff --git a/setup.cfg b/setup.cfg
index fa8a76a4..0553f9be 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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