From 10b87cb98ee7d98a77c29227c83b7344a92dd720 Mon Sep 17 00:00:00 2001 From: Yuriy Zveryanskyy Date: Thu, 17 Mar 2016 11:46:13 +0200 Subject: [PATCH] Add vendor methods for Intel Node Manager statistics Intel Node Manager is able to provide some statistical parameters directly without tools like ceilometer. This patch adds vendor methods for statistics. Change-Id: I9becb7e4f62c773d2a21fd53255acad3098b47a3 --- ironic_staging_drivers/common/exception.py | 4 + ironic_staging_drivers/intel_nm/__init__.py | 4 +- .../intel_nm/nm_commands.py | 120 +++++++++++++++++- ironic_staging_drivers/intel_nm/nm_vendor.py | 117 ++++++++++++----- .../intel_nm/statistics_schema.json | 24 ++++ .../tests/unit/intel_nm/test_commands.py | 65 ++++++++++ .../tests/unit/intel_nm/test_vendor.py | 68 +++++++++- ...m-statistics-methods-ddb803a9cf497f93.yaml | 4 + 8 files changed, 370 insertions(+), 36 deletions(-) create mode 100644 ironic_staging_drivers/intel_nm/statistics_schema.json create mode 100644 releasenotes/notes/add-nm-statistics-methods-ddb803a9cf497f93.yaml diff --git a/ironic_staging_drivers/common/exception.py b/ironic_staging_drivers/common/exception.py index e2f37e9..bb1ebe8 100644 --- a/ironic_staging_drivers/common/exception.py +++ b/ironic_staging_drivers/common/exception.py @@ -33,3 +33,7 @@ class AMTFailure(exception.IronicException): class LibvirtError(exception.IronicException): message = _("Libvirt call failed: %(err)s.") + + +class InvalidIPMITimestamp(exception.IronicException): + pass diff --git a/ironic_staging_drivers/intel_nm/__init__.py b/ironic_staging_drivers/intel_nm/__init__.py index f6b6cf1..c4491e4 100644 --- a/ironic_staging_drivers/intel_nm/__init__.py +++ b/ironic_staging_drivers/intel_nm/__init__.py @@ -52,7 +52,9 @@ class AgentAndIPMIToolIntelNMDriver(base.BaseDriver): 'get_nm_policy_suspend': self.nm_vendor, 'remove_nm_policy_suspend': self.nm_vendor, 'get_nm_capabilities': self.nm_vendor, - 'get_nm_version': self.nm_vendor} + 'get_nm_version': self.nm_vendor, + 'get_nm_statistics': self.nm_vendor, + 'reset_nm_statistics': self.nm_vendor} self.driver_passthru_mapping = {'lookup': self.agent_vendor} self.vendor = utils.MixinVendorInterface( self.mapping, diff --git a/ironic_staging_drivers/intel_nm/nm_commands.py b/ironic_staging_drivers/intel_nm/nm_commands.py index c73badb..a1e4bd9 100644 --- a/ironic_staging_drivers/intel_nm/nm_commands.py +++ b/ironic_staging_drivers/intel_nm/nm_commands.py @@ -12,14 +12,20 @@ import binascii import collections +import datetime import struct -from ironic.common import exception +from ironic.common import exception as ironic_exception +from oslo_log import log import six +from ironic_staging_drivers.common import exception from ironic_staging_drivers.common.i18n import _ +from ironic_staging_drivers.common.i18n import _LW +LOG = log.getLogger(__name__) + INTEL_NM_DOMAINS = { 'platform': 0x00, 'cpu': 0x01, @@ -84,6 +90,27 @@ IPMI_VERSIONS = { 0x03: '3.0' } +INTEL_NM_STATISTICS = { + 'global': { + 'power': 0x01, + 'temperature': 0x02, + 'throttling': 0x03, + 'airflow': 0x04, + 'airflow_temperature': 0x05, + 'chassis_power': 0x06, + 'unhandled_requests': 0x1B, + 'response_time': 0x1C, + 'cpu_throttling': 0x1D, # deprecated + 'memory_throttling': 0x1E, # deprecated + 'communication_failures': 0x1F + }, + 'policy': { + 'power': 0x11, + 'trigger': 0x12, + 'throttling': 0x13 + } +} + def _reverse_dict(d): return {v: k for k, v in d.items()} @@ -110,6 +137,12 @@ INTEL_NM_SUSPEND_SET = '0xC5' INTEL_NM_SUSPEND_GET = '0xC6' INTEL_NM_CAPABILITIES_GET = '0xC9' INTEL_NM_VERSION_GET = '0xCA' +INTEL_NM_STATISTICS_RESET = '0xC7' +INTEL_NM_STATISTICS_GET = '0xC8' + +_INVALID_TIME = datetime.datetime.utcfromtimestamp(0).isoformat() +_UNSPECIFIED_TIMESTAMP = 0xFFFFFFFF +_INIT_TIMESTAMP_MAX = 0x20000000 def _handle_parsing_error(func): @@ -121,11 +154,11 @@ def _handle_parsing_error(func): try: return func(raw_data) except (IndexError, struct.error): - raise exception.IPMIFailure(msg % _('has wrong length.')) + raise ironic_exception.IPMIFailure(msg % _('has wrong length.')) except KeyError: - raise exception.IPMIFailure(msg % _('is corrupted.')) + raise ironic_exception.IPMIFailure(msg % _('is corrupted.')) except ValueError: - raise exception.IPMIFailure(msg % _('cannot be converted.')) + raise ironic_exception.IPMIFailure(msg % _('cannot be converted.')) return wrapper @@ -189,6 +222,19 @@ def _days_parse(pattern): return [day for day in INTEL_NM_DAYS if pattern & INTEL_NM_DAYS[day]] +def _ipmi_timestamp_to_isotime(timestamp): + """Convert IPMI timestamp to iso8601.""" + if timestamp == _UNSPECIFIED_TIMESTAMP: + raise exception.InvalidIPMITimestamp(_('IPMI timestamp is invalid or ' + 'unspecified')) + if timestamp <= _INIT_TIMESTAMP_MAX: + raise exception.InvalidIPMITimestamp(_('IPMI initialization is not ' + 'completed, relative time is ' + '%d second') % timestamp) + + return datetime.datetime.utcfromtimestamp(timestamp).isoformat() + + def set_policy(policy): """Return hex data for policy set command.""" # NM defaults @@ -401,6 +447,72 @@ def parse_version(raw_data): return version +def reset_statistics(data): + """Return hex data for reset statistics command.""" + cmd = _create_command_head(INTEL_NM_STATISTICS_RESET) + global_scope = data['scope'] == 'global' + if 'parameter_name' in data: + # statistics parameter is set, get corresponding value + mode = INTEL_NM_STATISTICS['global'][data['parameter_name']] + # domain id should be always 0x00 for global reset by parameter name + data['domain_id'] = 'platform' + else: + mode = 0x00 if global_scope else 0x01 + _append_to_command(cmd, _hex(mode)) + if global_scope: + data['policy_id'] = 0x00 # will be ignored + _add_domain_policy_id(cmd, data) + + return cmd + + +def get_statistics(data): + """Return hex data for get statistics command.""" + cmd = _create_command_head(INTEL_NM_STATISTICS_GET) + scope = data['scope'] + _append_to_command(cmd, _hex( + INTEL_NM_STATISTICS[scope][data['parameter_name']])) + if scope == 'global': + data['policy_id'] = 0x00 # will be ignored + # case for "special" Node Manager global parameters (Mode 0x1B - 0x1F) + if 'domain_id' not in data: + data['domain_id'] = 'platform' # 0x00 + _add_domain_policy_id(cmd, data) + + return cmd + + +@_handle_parsing_error +def parse_statistics(raw_data): + """Parse statistics data.""" + statistics = {} + raw_int = _raw_to_int(raw_data) + + statistics_values = struct.unpack('