Add Intel Node Manager driver
This patch adds two new entry points with Intel Node Manager vendor interface: "agent_ipmitool_nm" and "fake_nm". New vendor interface supports Intel Node Manager policies. Change-Id: Iedbb3b906cef7bd5b2d768e926a59820ccd8c196
This commit is contained in:
parent
5ad7c7c925
commit
d5f031527f
62
ironic_staging_drivers/intel_nm/__init__.py
Normal file
62
ironic_staging_drivers/intel_nm/__init__.py
Normal file
@ -0,0 +1,62 @@
|
||||
# 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 ironic.drivers import base
|
||||
from ironic.drivers.modules import agent
|
||||
from ironic.drivers.modules import fake
|
||||
from ironic.drivers.modules import inspector
|
||||
from ironic.drivers.modules import ipmitool
|
||||
from ironic.drivers.modules import pxe
|
||||
from ironic.drivers import utils
|
||||
|
||||
from ironic_staging_drivers.intel_nm import nm_vendor
|
||||
|
||||
|
||||
class FakeIntelNMDriver(base.BaseDriver):
|
||||
"""Fake Intel NM driver."""
|
||||
|
||||
def __init__(self):
|
||||
self.power = fake.FakePower()
|
||||
self.deploy = fake.FakeDeploy()
|
||||
self.vendor = nm_vendor.IntelNMVendorPassthru()
|
||||
|
||||
|
||||
class AgentAndIPMIToolIntelNMDriver(base.BaseDriver):
|
||||
"""Agent + IPMITool driver with Intel NM policies."""
|
||||
def __init__(self):
|
||||
self.power = ipmitool.IPMIPower()
|
||||
self.boot = pxe.PXEBoot()
|
||||
self.deploy = agent.AgentDeploy()
|
||||
self.management = ipmitool.IPMIManagement()
|
||||
self.console = ipmitool.IPMIShellinaboxConsole()
|
||||
self.agent_vendor = agent.AgentVendorInterface()
|
||||
self.ipmi_vendor = ipmitool.VendorPassthru()
|
||||
self.nm_vendor = nm_vendor.IntelNMVendorPassthru()
|
||||
self.mapping = {'send_raw': self.ipmi_vendor,
|
||||
'bmc_reset': self.ipmi_vendor,
|
||||
'heartbeat': self.agent_vendor,
|
||||
'control_nm_policy': self.nm_vendor,
|
||||
'set_nm_policy': self.nm_vendor,
|
||||
'get_nm_policy': self.nm_vendor,
|
||||
'remove_nm_policy': self.nm_vendor,
|
||||
'set_nm_policy_suspend': self.nm_vendor,
|
||||
'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}
|
||||
self.driver_passthru_mapping = {'lookup': self.agent_vendor}
|
||||
self.vendor = utils.MixinVendorInterface(
|
||||
self.mapping,
|
||||
driver_passthru_mapping=self.driver_passthru_mapping)
|
||||
self.raid = agent.AgentRAID()
|
||||
self.inspect = inspector.Inspector.create_if_enabled(
|
||||
'AgentAndIPMIToolDriver')
|
25
ironic_staging_drivers/intel_nm/control_schema.json
Normal file
25
ironic_staging_drivers/intel_nm/control_schema.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"title": "Intel Node Manager policies control schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"scope": {
|
||||
"type": "string",
|
||||
"enum": ["global", "domain", "policy"]
|
||||
},
|
||||
"enable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"domain_id": {
|
||||
"type": "string",
|
||||
"enum": ["platform", "cpu", "memory", "io"]
|
||||
},
|
||||
"policy_id": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 255
|
||||
}
|
||||
},
|
||||
"required": ["scope", "enable"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
21
ironic_staging_drivers/intel_nm/get_cap_schema.json
Normal file
21
ironic_staging_drivers/intel_nm/get_cap_schema.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"title": "Intel Node Manager get capabilities schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"domain_id": {
|
||||
"type": "string",
|
||||
"enum": ["platform", "cpu", "memory", "io"]
|
||||
},
|
||||
"policy_trigger": {
|
||||
"type": "string",
|
||||
"enum": ["none", "temperature", "power", "reset", "boot"]
|
||||
},
|
||||
"power_domain": {
|
||||
"type": "string",
|
||||
"enum": ["primary", "secondary"]
|
||||
}
|
||||
},
|
||||
"required": ["domain_id", "policy_trigger", "power_domain" ],
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
85
ironic_staging_drivers/intel_nm/ipmi.py
Normal file
85
ironic_staging_drivers/intel_nm/ipmi.py
Normal file
@ -0,0 +1,85 @@
|
||||
# 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 ironic.common import exception
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.drivers.modules import ipmitool
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_log import log
|
||||
|
||||
from ironic_staging_drivers.common.i18n import _LE
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
# NOTE(yuriyz): there are extended version of send_raw() from Ironic ipmitool
|
||||
# driver and dump_sdr(). These functions depends on ipmitool driver internals,
|
||||
# and should be moved to Ironic for use from out-of-tree drivers.
|
||||
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def send_raw(task, raw_bytes):
|
||||
"""Send raw bytes to the BMC. Bytes should be a string of bytes.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:param raw_bytes: a string of raw bytes to send, e.g. '0x00 0x01'
|
||||
:returns: a tuple with stdout and stderr.
|
||||
:raises: IPMIFailure on an error from ipmitool.
|
||||
:raises: MissingParameterValue if a required parameter is missing.
|
||||
:raises: InvalidParameterValue when an invalid value is specified.
|
||||
|
||||
"""
|
||||
node_uuid = task.node.uuid
|
||||
LOG.debug('Sending node %(node)s raw bytes %(bytes)s',
|
||||
{'bytes': raw_bytes, 'node': node_uuid})
|
||||
driver_info = ipmitool._parse_driver_info(task.node)
|
||||
cmd = 'raw %s' % raw_bytes
|
||||
|
||||
try:
|
||||
out, err = ipmitool._exec_ipmitool(driver_info, cmd)
|
||||
LOG.debug('send raw bytes returned stdout: %(stdout)s, stderr:'
|
||||
' %(stderr)s', {'stdout': out, 'stderr': err})
|
||||
except (exception.PasswordFileFailedToCreate,
|
||||
processutils.ProcessExecutionError) as e:
|
||||
LOG.exception(_LE('IPMI "raw bytes" failed for node %(node_id)s '
|
||||
'with error: %(error)s.'),
|
||||
{'node_id': node_uuid, 'error': e})
|
||||
raise exception.IPMIFailure(cmd=cmd)
|
||||
|
||||
return out, err
|
||||
|
||||
|
||||
def dump_sdr(task, file_path):
|
||||
"""Dump SDR data to a file.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:param file_path: the path to SDR dump file.
|
||||
:raises: IPMIFailure on an error from ipmitool.
|
||||
:raises: MissingParameterValue if a required parameter is missing.
|
||||
:raises: InvalidParameterValue when an invalid value is specified.
|
||||
|
||||
"""
|
||||
node_uuid = task.node.uuid
|
||||
LOG.debug('Dump SDR data for node %(node)s to file %(name)s',
|
||||
{'name': file_path, 'node': node_uuid})
|
||||
driver_info = ipmitool._parse_driver_info(task.node)
|
||||
cmd = 'sdr dump %s' % file_path
|
||||
|
||||
try:
|
||||
out, err = ipmitool._exec_ipmitool(driver_info, cmd)
|
||||
LOG.debug('dump SDR returned stdout: %(stdout)s, stderr:'
|
||||
' %(stderr)s', {'stdout': out, 'stderr': err})
|
||||
except (exception.PasswordFileFailedToCreate,
|
||||
processutils.ProcessExecutionError) as e:
|
||||
LOG.exception(_LE('IPMI "sdr dump" failed for node %(node_id)s '
|
||||
'with error: %(error)s.'),
|
||||
{'node_id': node_uuid, 'error': e})
|
||||
raise exception.IPMIFailure(cmd=cmd)
|
17
ironic_staging_drivers/intel_nm/main_ids_schema.json
Normal file
17
ironic_staging_drivers/intel_nm/main_ids_schema.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"title": "Intel Node Manager domain and policy id json schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"domain_id": {
|
||||
"type": "string",
|
||||
"enum": ["platform", "cpu", "memory", "io"]
|
||||
},
|
||||
"policy_id": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 255
|
||||
}
|
||||
},
|
||||
"required": ["domain_id", "policy_id"],
|
||||
"additionalProperties": false
|
||||
}
|
429
ironic_staging_drivers/intel_nm/nm_commands.py
Normal file
429
ironic_staging_drivers/intel_nm/nm_commands.py
Normal file
@ -0,0 +1,429 @@
|
||||
# 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
|
||||
import collections
|
||||
import struct
|
||||
|
||||
from ironic.common import exception
|
||||
import six
|
||||
|
||||
from ironic_staging_drivers.common.i18n import _
|
||||
|
||||
|
||||
INTEL_NM_DOMAINS = {
|
||||
'platform': 0x00,
|
||||
'cpu': 0x01,
|
||||
'memory': 0x02,
|
||||
'protection': 0x03,
|
||||
'io': 0x04
|
||||
}
|
||||
|
||||
INTEL_NM_TRIGGERS = {
|
||||
'none': 0x00,
|
||||
'temperature': 0x01,
|
||||
'power': 0x02,
|
||||
'reset': 0x03,
|
||||
'boot': 0x04
|
||||
}
|
||||
|
||||
INTEL_NM_CPU_CORRECTION = {
|
||||
'auto': 0x00,
|
||||
'unagressive': 0x20,
|
||||
'aggressive': 0x40,
|
||||
}
|
||||
|
||||
INTEL_NM_STORAGE = {
|
||||
'persistent': 0x00,
|
||||
'volatile': 0x80,
|
||||
}
|
||||
|
||||
INTEL_NM_ACTIONS = {
|
||||
'alert': 0x00,
|
||||
'shutdown': 0x01,
|
||||
}
|
||||
|
||||
INTEL_NM_POWER_DOMAIN = {
|
||||
'primary': 0x00,
|
||||
'secondary': 0x80,
|
||||
}
|
||||
|
||||
INTEL_NM_BOOT_MODE = {
|
||||
'power': 0x00,
|
||||
'performance': 0x01,
|
||||
}
|
||||
|
||||
INTEL_NM_DAYS = collections.OrderedDict([('monday', 0x01),
|
||||
('tuesday', 0x02),
|
||||
('wednesday', 0x04),
|
||||
('thursday', 0x08),
|
||||
('friday', 0x10),
|
||||
('saturday', 0x20),
|
||||
('sunday', 0x40)])
|
||||
|
||||
VERSIONS = {
|
||||
0x01: '1.0',
|
||||
0x02: '1.5',
|
||||
0x03: '2.0',
|
||||
0x04: '2.5',
|
||||
0x05: '3.0'
|
||||
}
|
||||
|
||||
IPMI_VERSIONS = {
|
||||
0x01: '1.0',
|
||||
0x02: '2.0',
|
||||
0x03: '3.0'
|
||||
}
|
||||
|
||||
|
||||
def _reverse_dict(d):
|
||||
return {v: k for k, v in d.items()}
|
||||
|
||||
|
||||
INTEL_NM_DOMAINS_REV = _reverse_dict(INTEL_NM_DOMAINS)
|
||||
INTEL_NM_TRIGGERS_REV = _reverse_dict(INTEL_NM_TRIGGERS)
|
||||
INTEL_NM_CPU_CORRECTION_REV = _reverse_dict(INTEL_NM_CPU_CORRECTION)
|
||||
INTEL_NM_STORAGE_REV = _reverse_dict(INTEL_NM_STORAGE)
|
||||
INTEL_NM_ACTIONS_REV = _reverse_dict(INTEL_NM_ACTIONS)
|
||||
INTEL_NM_POWER_DOMAIN_REV = _reverse_dict(INTEL_NM_POWER_DOMAIN)
|
||||
|
||||
# OEM group extension code defined in IPMI spec
|
||||
INTEL_NM_NETFN = '0x2E'
|
||||
|
||||
# Intel manufacturer ID for OEM extension, LS byte first
|
||||
INTEL_NM_ID = ('0x57', '0x01', '0x00')
|
||||
|
||||
# Intel NM commands
|
||||
INTEL_NM_POLICY_CONTROL = '0xC0'
|
||||
INTEL_NM_POLICY_SET = '0xC1'
|
||||
INTEL_NM_POLICY_GET = '0xC2'
|
||||
INTEL_NM_SUSPEND_SET = '0xC5'
|
||||
INTEL_NM_SUSPEND_GET = '0xC6'
|
||||
INTEL_NM_CAPABILITIES_GET = '0xC9'
|
||||
INTEL_NM_VERSION_GET = '0xCA'
|
||||
|
||||
|
||||
def _handle_parsing_error(func):
|
||||
"""Decorator for handling errors in raw output data."""
|
||||
@six.wraps(func)
|
||||
def wrapper(raw_data):
|
||||
msg = _('Data from Intel Node Manager %s')
|
||||
|
||||
try:
|
||||
return func(raw_data)
|
||||
except (IndexError, struct.error):
|
||||
raise exception.IPMIFailure(msg % _('has wrong length.'))
|
||||
except KeyError:
|
||||
raise exception.IPMIFailure(msg % _('is corrupted.'))
|
||||
except ValueError:
|
||||
raise exception.IPMIFailure(msg % _('cannot be converted.'))
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def _hex(x):
|
||||
"""Formatting integer as two digit hex value."""
|
||||
return '0x{:02X}'.format(x)
|
||||
|
||||
|
||||
def _raw_to_int(raw_data):
|
||||
"""Converting list of raw hex values as strings to integers."""
|
||||
return [int(x, 16) for x in raw_data]
|
||||
|
||||
|
||||
def _bytehex(data):
|
||||
"""Iterate by one byte with hexlify() output."""
|
||||
for i in range(0, len(data), 2):
|
||||
yield data[i:i + 2]
|
||||
|
||||
|
||||
def _hexarray(data):
|
||||
"""Converting binary data to list of hex bytes as strings."""
|
||||
return ['0x' + x.decode() for x in _bytehex(binascii.hexlify(data))]
|
||||
|
||||
|
||||
def _append_to_command(cmd, data):
|
||||
"""Append list or single value to command."""
|
||||
if not isinstance(data, (list, tuple)):
|
||||
data = [data]
|
||||
cmd.extend(data)
|
||||
|
||||
|
||||
def _add_to_dict(data_dict, values, names):
|
||||
"""Add to dict values with corresponding names."""
|
||||
data_dict.update(dict(zip(names, values)))
|
||||
|
||||
|
||||
def _create_command_head(command):
|
||||
"""Create first part of Intel NM command."""
|
||||
cmd = [INTEL_NM_NETFN, command]
|
||||
_append_to_command(cmd, INTEL_NM_ID)
|
||||
return cmd
|
||||
|
||||
|
||||
def _add_domain_policy_id(cmd, data):
|
||||
"""Add domain id and policy id to command."""
|
||||
_append_to_command(cmd, _hex(INTEL_NM_DOMAINS[data['domain_id']]))
|
||||
_append_to_command(cmd, _hex(data['policy_id']))
|
||||
|
||||
|
||||
def _days_compose(days):
|
||||
"""Converting list of days to binary representation."""
|
||||
pattern = 0
|
||||
for day in days:
|
||||
pattern |= INTEL_NM_DAYS[day]
|
||||
return pattern
|
||||
|
||||
|
||||
def _days_parse(pattern):
|
||||
"""Parse binary data with days of week."""
|
||||
return [day for day in INTEL_NM_DAYS if pattern & INTEL_NM_DAYS[day]]
|
||||
|
||||
|
||||
def set_policy(policy):
|
||||
"""Return hex data for policy set command."""
|
||||
# NM defaults
|
||||
if 'cpu_power_correction' not in policy:
|
||||
policy['cpu_power_correction'] = 'auto'
|
||||
if 'storage' not in policy:
|
||||
policy['storage'] = 'persistent'
|
||||
if policy['policy_trigger'] in ('none', 'boot'):
|
||||
policy['trigger_limit'] = 0
|
||||
|
||||
cmd = _create_command_head(INTEL_NM_POLICY_SET)
|
||||
_append_to_command(cmd, _hex(INTEL_NM_DOMAINS[policy['domain_id']] |
|
||||
0x10 if policy['enable'] else 0x00))
|
||||
_append_to_command(cmd, _hex(policy['policy_id']))
|
||||
# 0x10 is policy add flag
|
||||
_append_to_command(cmd, _hex(INTEL_NM_TRIGGERS[policy['policy_trigger']] |
|
||||
INTEL_NM_CPU_CORRECTION[policy['cpu_power_correction']]
|
||||
| INTEL_NM_STORAGE[policy['storage']] | 0x10))
|
||||
_append_to_command(cmd, _hex(INTEL_NM_ACTIONS[policy['action']] |
|
||||
INTEL_NM_POWER_DOMAIN[policy['power_domain']]))
|
||||
|
||||
if isinstance(policy['target_limit'], int):
|
||||
limit = policy['target_limit']
|
||||
else:
|
||||
mode = 0x00 if policy['target_limit']['boot_mode'] == 'power' else 0x01
|
||||
cores_disabled = policy['target_limit']['cores_disabled'] << 1
|
||||
limit = mode | cores_disabled
|
||||
|
||||
policy_values = struct.pack('<HIHH', limit, policy['correction_time'],
|
||||
policy['trigger_limit'],
|
||||
policy['reporting_period'])
|
||||
_append_to_command(cmd, _hexarray(policy_values))
|
||||
|
||||
return cmd
|
||||
|
||||
|
||||
@_handle_parsing_error
|
||||
def parse_policy(raw_data):
|
||||
"""Parse policy data."""
|
||||
policy = {}
|
||||
raw_int = _raw_to_int(raw_data)
|
||||
|
||||
policy['domain_id'] = INTEL_NM_DOMAINS_REV[raw_int[3] & 0x0F]
|
||||
policy['enabled'] = bool(raw_int[3] & 0x10)
|
||||
policy['per_domain_enabled'] = bool(raw_int[3] & 0x20)
|
||||
policy['global_enabled'] = bool(raw_int[3] & 0x40)
|
||||
policy['created_by_nm'] = not bool(raw_int[3] & 0x80)
|
||||
policy['policy_trigger'] = INTEL_NM_TRIGGERS_REV[raw_int[4] & 0x0F]
|
||||
policy['power_policy'] = bool(raw_int[4] & 0x10)
|
||||
power_correction = INTEL_NM_CPU_CORRECTION_REV[raw_int[4] & 0x60]
|
||||
policy['cpu_power_correction'] = power_correction
|
||||
policy['storage'] = INTEL_NM_STORAGE_REV[raw_int[4] & 0x80]
|
||||
policy['action'] = INTEL_NM_ACTIONS_REV[raw_int[5] & 0x01]
|
||||
policy['power_domain'] = INTEL_NM_POWER_DOMAIN_REV[raw_int[5] & 0x80]
|
||||
policy_values = struct.unpack('<HIHH', bytearray(raw_int[6:]))
|
||||
policy_names = ('target_limit', 'correction_time', 'trigger_limit',
|
||||
'reporting_period')
|
||||
_add_to_dict(policy, policy_values, policy_names)
|
||||
|
||||
return policy
|
||||
|
||||
|
||||
def set_policy_suspend(suspend):
|
||||
"""Return hex data for policy suspend set command."""
|
||||
cmd = _create_command_head(INTEL_NM_SUSPEND_SET)
|
||||
_add_domain_policy_id(cmd, suspend)
|
||||
periods = suspend['periods']
|
||||
_append_to_command(cmd, _hex(len(periods)))
|
||||
|
||||
for period in periods:
|
||||
_append_to_command(cmd, _hex(period['start']))
|
||||
_append_to_command(cmd, _hex(period['stop']))
|
||||
_append_to_command(cmd, _hex(_days_compose(period['days'])))
|
||||
|
||||
return cmd
|
||||
|
||||
|
||||
@_handle_parsing_error
|
||||
def parse_policy_suspend(raw_data):
|
||||
"""Parse policy suspend data."""
|
||||
suspends = []
|
||||
raw_int = _raw_to_int(raw_data)
|
||||
|
||||
policy_num = raw_int[3]
|
||||
for num in range(policy_num):
|
||||
base = num * 3 + 4
|
||||
suspend = {
|
||||
"start": raw_int[base],
|
||||
"stop": raw_int[base + 1],
|
||||
"days": _days_parse(raw_int[base + 2])
|
||||
}
|
||||
suspends.append(suspend)
|
||||
|
||||
return suspends
|
||||
|
||||
|
||||
def get_capabilities(data):
|
||||
"""Return hex data for capabilities get command."""
|
||||
cmd = _create_command_head(INTEL_NM_CAPABILITIES_GET)
|
||||
_append_to_command(cmd, _hex(INTEL_NM_DOMAINS[data['domain_id']]))
|
||||
power_policy = 0x10
|
||||
_append_to_command(cmd, _hex(INTEL_NM_TRIGGERS[data['policy_trigger']] |
|
||||
power_policy |
|
||||
INTEL_NM_POWER_DOMAIN[data['power_domain']]))
|
||||
|
||||
return cmd
|
||||
|
||||
|
||||
@_handle_parsing_error
|
||||
def parse_capabilities(raw_data):
|
||||
"""Parse capabilities data."""
|
||||
capabilities = {}
|
||||
raw_int = _raw_to_int(raw_data)
|
||||
|
||||
capabilities['max_policies'] = raw_int[3]
|
||||
capabilities_values = struct.unpack('<HHIIHH', bytearray(
|
||||
raw_int[4:20]))
|
||||
capabilities_names = ('max_limit_value', 'min_limit_value',
|
||||
'min_correction_time', 'max_correction_time',
|
||||
'min_reporting_period', 'max_reporting_period')
|
||||
_add_to_dict(capabilities, capabilities_values, capabilities_names)
|
||||
capabilities['domain_id'] = INTEL_NM_DOMAINS_REV[raw_int[20] & 0x0F]
|
||||
power_domain = INTEL_NM_POWER_DOMAIN_REV[raw_int[20] & 0x80]
|
||||
capabilities['power_domain'] = power_domain
|
||||
|
||||
return capabilities
|
||||
|
||||
|
||||
def control_policies(control_data):
|
||||
"""Return hex data for enable or disable policy command."""
|
||||
cmd = _create_command_head(INTEL_NM_POLICY_CONTROL)
|
||||
|
||||
enable = control_data['enable']
|
||||
scope = control_data['scope']
|
||||
|
||||
if scope == 'global':
|
||||
flags = '0x01' if enable else '0x00'
|
||||
domain_id = 0
|
||||
policy_id = 0
|
||||
elif scope == 'domain':
|
||||
flags = '0x03' if enable else '0x02'
|
||||
domain_id = INTEL_NM_DOMAINS[control_data['domain_id']]
|
||||
policy_id = 0
|
||||
elif scope == 'policy':
|
||||
flags = '0x05' if enable else '0x04'
|
||||
domain_id = INTEL_NM_DOMAINS[control_data['domain_id']]
|
||||
policy_id = control_data['policy_id']
|
||||
|
||||
_append_to_command(cmd, flags)
|
||||
_append_to_command(cmd, _hex(domain_id))
|
||||
_append_to_command(cmd, _hex(policy_id))
|
||||
|
||||
return cmd
|
||||
|
||||
|
||||
def get_policy(data):
|
||||
"""Return hex data for policy get command."""
|
||||
cmd = _create_command_head(INTEL_NM_POLICY_GET)
|
||||
_add_domain_policy_id(cmd, data)
|
||||
|
||||
return cmd
|
||||
|
||||
|
||||
def remove_policy(data):
|
||||
"""Return hex data for policy remove command."""
|
||||
cmd = _create_command_head(INTEL_NM_POLICY_SET)
|
||||
_add_domain_policy_id(cmd, data)
|
||||
# first 0 is remove policy, extra will be ignored
|
||||
_append_to_command(cmd, ('0x00',) * 12)
|
||||
|
||||
return cmd
|
||||
|
||||
|
||||
def get_policy_suspend(data):
|
||||
"""Return hex data for policy get suspend command."""
|
||||
cmd = _create_command_head(INTEL_NM_SUSPEND_GET)
|
||||
_add_domain_policy_id(cmd, data)
|
||||
|
||||
return cmd
|
||||
|
||||
|
||||
def remove_policy_suspend(data):
|
||||
"""Return hex data for policy remove suspend command."""
|
||||
cmd = _create_command_head(INTEL_NM_SUSPEND_SET)
|
||||
_add_domain_policy_id(cmd, data)
|
||||
# remove suspend
|
||||
_append_to_command(cmd, '0x00')
|
||||
|
||||
return cmd
|
||||
|
||||
|
||||
def get_version(data):
|
||||
"""Return hex data for version get command."""
|
||||
cmd = _create_command_head(INTEL_NM_VERSION_GET)
|
||||
|
||||
return cmd
|
||||
|
||||
|
||||
@_handle_parsing_error
|
||||
def parse_version(raw_data):
|
||||
"""Parse versions data."""
|
||||
version = {}
|
||||
raw_int = _raw_to_int(raw_data)
|
||||
|
||||
version['nm'] = VERSIONS.get(raw_int[3], 'unknown')
|
||||
version['ipmi'] = IPMI_VERSIONS.get(raw_int[4], 'unknown')
|
||||
version['patch'] = str(raw_int[5])
|
||||
version['firmware'] = str(raw_int[6]) + '.' + str(raw_int[7])
|
||||
|
||||
return version
|
||||
|
||||
|
||||
# Code below taken from Ceilometer
|
||||
# Copyright 2014 Intel Corporation.
|
||||
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.
|
||||
"""
|
||||
prefix = '5701000d01'
|
||||
# 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:
|
||||
data_str = binascii.hexlify(bin_fp.read())
|
||||
|
||||
if six.PY3:
|
||||
data_str = data_str.decode()
|
||||
oem_id_index = data_str.find(prefix)
|
||||
if oem_id_index != -1:
|
||||
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.
|
||||
return ('0x' + ret[0:2], '0x0' + ret[2])
|
294
ironic_staging_drivers/intel_nm/nm_vendor.py
Normal file
294
ironic_staging_drivers/intel_nm/nm_vendor.py
Normal file
@ -0,0 +1,294 @@
|
||||
# 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 json
|
||||
import os
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.drivers import base
|
||||
from ironic_lib import utils as ironic_utils
|
||||
import jsonschema
|
||||
from jsonschema import exceptions as json_schema_exc
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_utils import excutils
|
||||
import six
|
||||
|
||||
from ironic_staging_drivers.common.i18n import _
|
||||
from ironic_staging_drivers.common.i18n import _LE
|
||||
from ironic_staging_drivers.common.i18n import _LI
|
||||
from ironic_staging_drivers.intel_nm import ipmi
|
||||
from ironic_staging_drivers.intel_nm import nm_commands
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.import_opt('tempdir', 'ironic.common.utils')
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
SCHEMAS = ('control_schema', 'get_cap_schema', 'main_ids_schema',
|
||||
'policy_schema', 'suspend_schema')
|
||||
|
||||
|
||||
def _command_to_string(cmd):
|
||||
"""Convert a list with command raw bytes to string."""
|
||||
return ' '.join(cmd)
|
||||
|
||||
|
||||
def _get_nm_address(task):
|
||||
"""Get Intel Node Manager target channel and address.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:raises: IPMIFailure if Intel Node Manager is not detected on a node or if
|
||||
an error happens during detection.
|
||||
:returns: a tuple with IPMI channel and address of Intel Node Manager.
|
||||
"""
|
||||
node = task.node
|
||||
driver_internal_info = node.driver_internal_info
|
||||
|
||||
def _save_to_node(channel, address):
|
||||
driver_internal_info['intel_nm_channel'] = channel
|
||||
driver_internal_info['intel_nm_address'] = address
|
||||
node.driver_internal_info = driver_internal_info
|
||||
node.save()
|
||||
|
||||
channel = driver_internal_info.get('intel_nm_channel')
|
||||
address = driver_internal_info.get('intel_nm_address')
|
||||
if channel and address:
|
||||
return channel, address
|
||||
if channel is False and address is False:
|
||||
raise exception.IPMIFailure(_('Driver data indicates that Intel '
|
||||
'Node Manager detection failed.'))
|
||||
LOG.info(_LI('Start detection of Intel Node Manager on node %s'),
|
||||
node.uuid)
|
||||
sdr_filename = os.path.join(CONF.tempdir, node.uuid + '.sdr')
|
||||
res = None
|
||||
try:
|
||||
ipmi.dump_sdr(task, sdr_filename)
|
||||
res = nm_commands.parse_slave_and_channel(sdr_filename)
|
||||
finally:
|
||||
ironic_utils.unlink_without_raise(sdr_filename)
|
||||
if res is None:
|
||||
_save_to_node(False, False)
|
||||
raise exception.IPMIFailure(_('Intel Node Manager is not detected.'))
|
||||
address, channel = res
|
||||
LOG.debug('Intel Node Manager sensors present in SDR on node %(node)s, '
|
||||
'channel %(channel)s address %(address)s.',
|
||||
{'node': node.uuid, 'channel': channel, 'address': address})
|
||||
# SDR can contain wrong info, try simple command
|
||||
node.driver_info['ipmi_bridging'] = 'single'
|
||||
node.driver_info['ipmi_target_channel'] = channel
|
||||
node.driver_info['ipmi_target_address'] = address
|
||||
try:
|
||||
ipmi.send_raw(task, _command_to_string(nm_commands.get_version(None)))
|
||||
_save_to_node(channel, address)
|
||||
return channel, address
|
||||
except exception.IPMIFailure:
|
||||
_save_to_node(False, False)
|
||||
raise exception.IPMIFailure(_('Intel Node Manager sensors record '
|
||||
'present in SDR but Node Manager is not '
|
||||
'responding.'))
|
||||
|
||||
|
||||
def _execute_nm_command(task, data, command_func, parse_func=None):
|
||||
"""Execute Intel Node Manager command via send_raw().
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:param data: a dict with data passed to vendor's method.
|
||||
:param command_func: a function that returns raw command bytes.
|
||||
:param parse_func: a function that parses returned raw bytes.
|
||||
:raises: IPMIFailure if Intel Node Manager is not detected on a node or if
|
||||
an error happens during command execution.
|
||||
:returns: a dict with parsed output or None if command does not return
|
||||
user's info.
|
||||
"""
|
||||
try:
|
||||
channel, address = _get_nm_address(task)
|
||||
except exception.IPMIFailure as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception(_LE('Can not obtain Intel Node Manager address for '
|
||||
'node %(node)s: %(err)s'),
|
||||
{'node': task.node.uuid, 'err': six.text_type(e)})
|
||||
driver_info = task.node.driver_info
|
||||
driver_info['ipmi_bridging'] = 'single'
|
||||
driver_info['ipmi_target_channel'] = channel
|
||||
driver_info['ipmi_target_address'] = address
|
||||
cmd = _command_to_string(command_func(data))
|
||||
out = ipmi.send_raw(task, cmd)[0]
|
||||
if parse_func:
|
||||
try:
|
||||
return parse_func(out.split())
|
||||
except exception.IPMIFailure as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception(_LE('Error in returned data for node %(node)s: '
|
||||
'%(err)s'), {'node': task.node.uuid,
|
||||
'err': six.text_type(e)})
|
||||
|
||||
|
||||
class IntelNMVendorPassthru(base.VendorInterface):
|
||||
"""Intel Node Manager policies vendor interface."""
|
||||
|
||||
def __init__(self):
|
||||
schemas_dir = os.path.dirname(__file__)
|
||||
for schema in SCHEMAS:
|
||||
filename = os.path.join(schemas_dir, schema + '.json')
|
||||
with open(filename, 'r') as sf:
|
||||
setattr(self, schema, json.load(sf))
|
||||
|
||||
def get_properties(self):
|
||||
"""Returns the properties of the interface.."""
|
||||
return {}
|
||||
|
||||
def validate(self, task, method, http_method, **kwargs):
|
||||
"""Validates the vendor method's parameters.
|
||||
|
||||
This method validates whether the supplied data contains the required
|
||||
information for the driver.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:param method: name of vendor method.
|
||||
:param http_method: HTTP method.
|
||||
:param kwargs: data passed to vendor's method.
|
||||
:raises: InvalidParameterValue if supplied data is not valid.
|
||||
:raises: MissingParameterValue if parameters missing in supplied data.
|
||||
"""
|
||||
try:
|
||||
if method in ('get_nm_policy', 'remove_nm_policy',
|
||||
'get_nm_policy_suspend', 'remove_nm_policy_suspend'):
|
||||
jsonschema.validate(kwargs, self.main_ids_schema)
|
||||
|
||||
elif method == 'control_nm_policy':
|
||||
jsonschema.validate(kwargs, self.control_schema)
|
||||
no_domain = _('Missing "domain_id"')
|
||||
no_policy = _('Missing "policy_id"')
|
||||
if kwargs['scope'] == 'domain' and not kwargs.get('domain_id'):
|
||||
raise exception.MissingParameterValue(no_domain)
|
||||
if kwargs['scope'] == 'policy':
|
||||
if not kwargs.get('domain_id'):
|
||||
raise exception.MissingParameterValue(no_domain)
|
||||
if not kwargs.get('policy_id'):
|
||||
raise exception.MissingParameterValue(no_policy)
|
||||
|
||||
elif method == 'set_nm_policy':
|
||||
jsonschema.validate(kwargs, self.policy_schema)
|
||||
if kwargs['policy_trigger'] == 'boot':
|
||||
if not isinstance(kwargs['target_limit'], dict):
|
||||
raise exception.InvalidParameterValue(_('Invalid boot '
|
||||
'policy'))
|
||||
|
||||
elif method == 'set_nm_policy_suspend':
|
||||
jsonschema.validate(kwargs, self.suspend_schema)
|
||||
|
||||
elif method == 'get_nm_capabilities':
|
||||
jsonschema.validate(kwargs, self.get_cap_schema)
|
||||
|
||||
except json_schema_exc.ValidationError as e:
|
||||
raise exception.InvalidParameterValue(_('Input data validation '
|
||||
'error: %s') % e)
|
||||
|
||||
@base.passthru(['PUT'])
|
||||
def control_nm_policy(self, task, **kwargs):
|
||||
"""Enable or disable Intel Node Manager policy control.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:param kwargs: data passed to method.
|
||||
:raises: IPMIFailure on an error.
|
||||
"""
|
||||
_execute_nm_command(task, kwargs, nm_commands.control_policies)
|
||||
|
||||
@base.passthru(['PUT'])
|
||||
def set_nm_policy(self, task, **kwargs):
|
||||
"""Set Intel Node Manager policy.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:param kwargs: data passed to method.
|
||||
:raises: IPMIFailure on an error.
|
||||
"""
|
||||
_execute_nm_command(task, kwargs, nm_commands.set_policy)
|
||||
|
||||
@base.passthru(['GET'], async=False)
|
||||
def get_nm_policy(self, task, **kwargs):
|
||||
"""Get Intel Node Manager policy.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:param kwargs: data passed to method.
|
||||
:raises: IPMIFailure on an error.
|
||||
:returns: a dictionary containing policy settings.
|
||||
"""
|
||||
return _execute_nm_command(task, kwargs, nm_commands.get_policy,
|
||||
nm_commands.parse_policy)
|
||||
|
||||
@base.passthru(['DELETE'])
|
||||
def remove_nm_policy(self, task, **kwargs):
|
||||
"""Remove Intel Node Manager policy.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:param kwargs: data passed to method.
|
||||
:raises: IPMIFailure on an error.
|
||||
"""
|
||||
_execute_nm_command(task, kwargs, nm_commands.remove_policy)
|
||||
|
||||
@base.passthru(['PUT'])
|
||||
def set_nm_policy_suspend(self, task, **kwargs):
|
||||
"""Set Intel Node Manager policy suspend periods.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:param kwargs: data passed to method.
|
||||
:raises: IPMIFailure on an error.
|
||||
"""
|
||||
_execute_nm_command(task, kwargs, nm_commands.set_policy_suspend)
|
||||
|
||||
@base.passthru(['GET'], async=False)
|
||||
def get_nm_policy_suspend(self, task, **kwargs):
|
||||
"""Get Intel Node Manager policy suspend periods.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:param kwargs: data passed to method.
|
||||
:raises: IPMIFailure on an error.
|
||||
:returns: a dictionary containing suspend info for a policy.
|
||||
"""
|
||||
return _execute_nm_command(task, kwargs,
|
||||
nm_commands.get_policy_suspend,
|
||||
nm_commands.parse_policy_suspend)
|
||||
|
||||
@base.passthru(['DELETE'])
|
||||
def remove_nm_policy_suspend(self, task, **kwargs):
|
||||
"""Remove Intel Node Manager policy suspend periods.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:param kwargs: data passed to method.
|
||||
:raises: IPMIFailure on an error.
|
||||
"""
|
||||
_execute_nm_command(task, kwargs, nm_commands.remove_policy_suspend)
|
||||
|
||||
@base.passthru(['GET'], async=False)
|
||||
def get_nm_capabilities(self, task, **kwargs):
|
||||
"""Get Intel Node Manager capabilities.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:param kwargs: data passed to method.
|
||||
:raises: IPMIFailure on an error.
|
||||
:returns: a dictionary containing Intel NM capabilities.
|
||||
"""
|
||||
return _execute_nm_command(task, kwargs, nm_commands.get_capabilities,
|
||||
nm_commands.parse_capabilities)
|
||||
|
||||
@base.passthru(['GET'], async=False)
|
||||
def get_nm_version(self, task, **kwargs):
|
||||
"""Get Intel Node Manager version.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:param kwargs: data passed to method.
|
||||
:raises: IPMIFailure on an error.
|
||||
:returns: a dictionary containing Intel NM version.
|
||||
"""
|
||||
return _execute_nm_command(task, kwargs, nm_commands.get_version,
|
||||
nm_commands.parse_version)
|
79
ironic_staging_drivers/intel_nm/policy_schema.json
Normal file
79
ironic_staging_drivers/intel_nm/policy_schema.json
Normal file
@ -0,0 +1,79 @@
|
||||
{
|
||||
"title": "Intel Node Manager policy json schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"domain_id": {
|
||||
"type": "string",
|
||||
"enum": ["platform", "cpu", "memory", "io"]
|
||||
},
|
||||
"enable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"policy_id": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 255
|
||||
},
|
||||
"policy_trigger": {
|
||||
"type": "string",
|
||||
"enum": ["none", "temperature", "power", "reset", "boot"]
|
||||
},
|
||||
"cpu_power_correction": {
|
||||
"type": "string",
|
||||
"enum": ["auto", "unagressive", "aggressive"]
|
||||
},
|
||||
"storage": {
|
||||
"type": "string",
|
||||
"enum": ["persistent", "volatile"]
|
||||
},
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": ["alert", "shutdown"]
|
||||
},
|
||||
"power_domain": {
|
||||
"type": "string",
|
||||
"enum": ["primary", "secondary"]
|
||||
},
|
||||
"target_limit": {
|
||||
"anyOf": [{
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 65535
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"boot_mode": {
|
||||
"type": "string",
|
||||
"enum": ["power", "performance"]
|
||||
},
|
||||
"cores_disabled": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 127
|
||||
}
|
||||
},
|
||||
"required": ["boot_mode", "cores_disabled"],
|
||||
"additionalProperties": false
|
||||
|
||||
}]
|
||||
},
|
||||
"correction_time": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"trigger_limit": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 65535
|
||||
},
|
||||
"reporting_period": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 65535
|
||||
}
|
||||
},
|
||||
"required": ["domain_id", "enable", "policy_id", "policy_trigger", "action", "power_domain", "target_limit", "correction_time", "reporting_period" ],
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
48
ironic_staging_drivers/intel_nm/suspend_schema.json
Normal file
48
ironic_staging_drivers/intel_nm/suspend_schema.json
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"title": "Intel Node Manager policy suspend periods json schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"domain_id": {
|
||||
"type": "string",
|
||||
"enum": ["platform", "cpu", "memory", "io"]
|
||||
},
|
||||
"policy_id": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 255
|
||||
},
|
||||
"periods": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 240
|
||||
},
|
||||
"stop": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 240
|
||||
},
|
||||
"days": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]
|
||||
},
|
||||
"uniqueItems": true,
|
||||
"minItems": 1
|
||||
}
|
||||
},
|
||||
"required": ["start", "stop", "days" ],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"minItems": 1,
|
||||
"maxItems": 5
|
||||
}
|
||||
},
|
||||
"required": ["domain_id", "policy_id", "periods" ],
|
||||
"additionalProperties": false
|
||||
}
|
265
ironic_staging_drivers/tests/unit/intel_nm/test_commands.py
Normal file
265
ironic_staging_drivers/tests/unit/intel_nm/test_commands.py
Normal file
@ -0,0 +1,265 @@
|
||||
# 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 Intel NM policies commands
|
||||
"""
|
||||
|
||||
import tempfile
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.tests import base
|
||||
|
||||
from ironic_staging_drivers.intel_nm import nm_commands as commands
|
||||
|
||||
|
||||
@commands._handle_parsing_error
|
||||
def fake_parse(fake_data):
|
||||
return fake_data
|
||||
|
||||
|
||||
@commands._handle_parsing_error
|
||||
def fake_parse_exc(d):
|
||||
raise IndexError()
|
||||
|
||||
|
||||
class ParsingErrorDecoratorTestCase(base.TestCase):
|
||||
|
||||
def test_parse_no_errors(self):
|
||||
self.assertEqual('foo', fake_parse('foo'))
|
||||
|
||||
def test_parse_handled_exception(self):
|
||||
self.assertRaises(exception.IPMIFailure, fake_parse_exc, 'foo')
|
||||
|
||||
|
||||
class IntelNMPoliciesCommandTestCase(base.TestCase):
|
||||
|
||||
def test_set_policy(self):
|
||||
policy = {'domain_id': 'platform', 'enable': True, 'policy_id': 123,
|
||||
'policy_trigger': 'temperature',
|
||||
'cpu_power_correction': 'auto', 'storage': 'persistent',
|
||||
'action': 'alert', 'power_domain': 'primary',
|
||||
'target_limit': 1000, 'correction_time': 2000,
|
||||
'trigger_limit': 100, 'reporting_period': 600}
|
||||
expected = ['0x2E', '0xC1', '0x57', '0x01', '0x00', '0x10', '0x7B',
|
||||
'0x11', '0x00', '0xe8', '0x03', '0xd0', '0x07', '0x00',
|
||||
'0x00', '0x64', '0x00', '0x58', '0x02']
|
||||
result = commands.set_policy(policy)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_set_policy_with_defaults(self):
|
||||
policy = {'domain_id': 'platform', 'enable': True, 'policy_id': 123,
|
||||
'policy_trigger': 'none', 'action': 'alert',
|
||||
'power_domain': 'primary', 'target_limit': 1000,
|
||||
'correction_time': 2000, 'reporting_period': 600}
|
||||
expected = ['0x2E', '0xC1', '0x57', '0x01', '0x00', '0x10', '0x7B',
|
||||
'0x10', '0x00', '0xe8', '0x03', '0xd0', '0x07', '0x00',
|
||||
'0x00', '0x00', '0x00', '0x58', '0x02']
|
||||
result = commands.set_policy(policy)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_set_policy_boot(self):
|
||||
policy = {'domain_id': 'platform', 'enable': True, 'policy_id': 123,
|
||||
'policy_trigger': 'boot', 'cpu_power_correction': 'auto',
|
||||
'storage': 'persistent', 'action': 'alert',
|
||||
'power_domain': 'primary',
|
||||
'target_limit': {'boot_mode': 'power', 'cores_disabled': 2},
|
||||
'correction_time': 2000, 'trigger_limit': 100,
|
||||
'reporting_period': 600}
|
||||
expected = ['0x2E', '0xC1', '0x57', '0x01', '0x00', '0x10', '0x7B',
|
||||
'0x14', '0x00', '0x04', '0x00', '0xd0', '0x07', '0x00',
|
||||
'0x00', '0x00', '0x00', '0x58', '0x02']
|
||||
result = commands.set_policy(policy)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_set_policy_suspend(self):
|
||||
suspend = {'domain_id': 'platform', 'policy_id': 123,
|
||||
'periods': [{'start': 20, 'stop': 100,
|
||||
'days': ['monday', 'tuesday']},
|
||||
{'start': 30, 'stop': 150,
|
||||
'days': ['friday', 'sunday']}]}
|
||||
result = commands.set_policy_suspend(suspend)
|
||||
expected = ['0x2E', '0xC5', '0x57', '0x01', '0x00', '0x00', '0x7B',
|
||||
'0x02', '0x14', '0x64', '0x03', '0x1E', '0x96', '0x50']
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_get_capabilities(self):
|
||||
cap_data = {'domain_id': 'platform', 'policy_trigger': 'none',
|
||||
'power_domain': 'primary'}
|
||||
result = commands.get_capabilities(cap_data)
|
||||
expected = ['0x2E', '0xC9', '0x57', '0x01', '0x00', '0x00', '0x10']
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_control_policies(self):
|
||||
control_data = {'scope': 'policy', 'enable': True,
|
||||
'domain_id': 'platform', 'policy_id': 123}
|
||||
result = commands.control_policies(control_data)
|
||||
expected = ['0x2E', '0xC0', '0x57', '0x01', '0x00', '0x05', '0x00',
|
||||
'0x7B']
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_get_policy(self):
|
||||
data = {'domain_id': 'platform', 'policy_id': 123}
|
||||
result = commands.get_policy(data)
|
||||
expected = ['0x2E', '0xC2', '0x57', '0x01', '0x00', '0x00', '0x7B']
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_remove_policy(self):
|
||||
data = {'domain_id': 'platform', 'policy_id': 123}
|
||||
expected = (['0x2E', '0xC1', '0x57', '0x01', '0x00', '0x00', '0x7B'] +
|
||||
['0x00'] * 12)
|
||||
result = commands.remove_policy(data)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_get_policy_suspend(self):
|
||||
data = {'domain_id': 'platform', 'policy_id': 123}
|
||||
expected = ['0x2E', '0xC6', '0x57', '0x01', '0x00', '0x00', '0x7B']
|
||||
result = commands.get_policy_suspend(data)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_remove_policy_suspend(self):
|
||||
data = {'domain_id': 'platform', 'policy_id': 123}
|
||||
expected = ['0x2E', '0xC5', '0x57', '0x01', '0x00', '0x00', '0x7B',
|
||||
'0x00']
|
||||
result = commands.remove_policy_suspend(data)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_get_version(self):
|
||||
result = commands.get_version(None)
|
||||
expected = ['0x2E', '0xCA', '0x57', '0x01', '0x00']
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_parse_policy(self):
|
||||
raw_data = ['0x00', '0x00', '0x00', '0x70', '0x00', '0x00', '0x02',
|
||||
'0xFF', '0x00', '0x01', '0x02', '0x00', '0x01', '0x20',
|
||||
'0x40', '0x01']
|
||||
expected = {'action': 'alert', 'correction_time': 131328,
|
||||
'cpu_power_correction': 'auto', 'created_by_nm': True,
|
||||
'domain_id': 'platform', 'enabled': True,
|
||||
'global_enabled': True, 'per_domain_enabled': True,
|
||||
'policy_trigger': 'none', 'power_domain': 'primary',
|
||||
'power_policy': False, 'reporting_period': 320,
|
||||
'storage': 'persistent', 'target_limit': 65282,
|
||||
'trigger_limit': 8193}
|
||||
|
||||
result = commands.parse_policy(raw_data)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_parse_policy_invalid_length(self):
|
||||
raw_data = ['0x00', '0x00', '0x00', '0x70', '0x00', '0x00', '0x02',
|
||||
'0xFF', '0x00', '0x01', '0x02', '0x00', '0x01', '0x20']
|
||||
self.assertRaises(exception.IPMIFailure, commands.parse_policy,
|
||||
raw_data)
|
||||
|
||||
def test_parse_policy_corrupted_data(self):
|
||||
raw_data = ['0x00', '0x00', '0x00', '0x7F', '0x00', '0x00', '0x02',
|
||||
'0xFF', '0x00', '0x01', '0x02', '0x00', '0x01', '0x20',
|
||||
'0x40', '0x01']
|
||||
self.assertRaises(exception.IPMIFailure, commands.parse_policy,
|
||||
raw_data)
|
||||
|
||||
def test_parse_policy_conversion_error(self):
|
||||
raw_data = ['0x00', '0x00', '0x00', 'boo', '0x00', '0x00', '0x02',
|
||||
'0xFF', '0x00', '0x01', '0x02', '0x00', '0x01', '0x20',
|
||||
'0x40', '0x01']
|
||||
self.assertRaises(exception.IPMIFailure, commands.parse_policy,
|
||||
raw_data)
|
||||
|
||||
def test_parse_policy_suspend(self):
|
||||
raw_data = ['0x00', '0x00', '0x00', '0x02', '0x08', '0x18', '0x03',
|
||||
'0x20', '0x50', '0x18']
|
||||
expected = [{'days': ['monday', 'tuesday'], 'start': 8, 'stop': 24},
|
||||
{'days': ['thursday', 'friday'], 'start': 32, 'stop': 80}]
|
||||
result = commands.parse_policy_suspend(raw_data)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_parse_policy_suspend_invalid_lenght(self):
|
||||
raw_data = ['0x00', '0x00', '0x00', '0x22', '0x08', '0x18', '0x03']
|
||||
self.assertRaises(exception.IPMIFailure, commands.parse_policy_suspend,
|
||||
raw_data)
|
||||
|
||||
def test_parse_policy_suspend_conversion_error(self):
|
||||
raw_data = ['0x00', '0x00', '0x00', '0x02', 'boo', '0x18', '0x03',
|
||||
'0x20', '0x50', '0x18']
|
||||
self.assertRaises(exception.IPMIFailure, commands.parse_policy_suspend,
|
||||
raw_data)
|
||||
|
||||
def test_parse_capabilities(self):
|
||||
raw_data = ['0x00', '0x00', '0x00', '0x10', '0x00', '0x10', '0x00',
|
||||
'0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00',
|
||||
'0x80', '0x00', '0x00', '0x00', '0x00', '0x80', '0x00']
|
||||
expected = {'domain_id': 'platform', 'max_correction_time': 8388608,
|
||||
'max_limit_value': 4096, 'max_policies': 16,
|
||||
'max_reporting_period': 32768, 'min_correction_time': 0,
|
||||
'min_limit_value': 0, 'min_reporting_period': 0,
|
||||
'power_domain': 'primary'}
|
||||
result = commands.parse_capabilities(raw_data)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_parse_capabilities_invalid_lenght(self):
|
||||
raw_data = ['0x00', '0x00', '0x00', '0x10', '0x00', '0x10', '0x00']
|
||||
self.assertRaises(exception.IPMIFailure, commands.parse_capabilities,
|
||||
raw_data)
|
||||
|
||||
def test_parse_capabilities_corrupted_data(self):
|
||||
raw_data = ['0x00', '0x00', '0x00', '0x10', '0x00', '0x10', '0x00',
|
||||
'0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00',
|
||||
'0x80', '0x00', '0x00', '0x00', '0x00', '0x80', '0xFF']
|
||||
self.assertRaises(exception.IPMIFailure, commands.parse_capabilities,
|
||||
raw_data)
|
||||
|
||||
def test_parse_capabilities_conversion_error(self):
|
||||
raw_data = ['0x00', '0x00', '0x00', '0x10', '0x00', '0x10', '0x00',
|
||||
'0x00', '0x00', '0x00', '0x00', '0x00', '0x00', '0x00',
|
||||
'0x80', '0x00', '0x00', '0x00', 'boo', '0x80', '0x00']
|
||||
self.assertRaises(exception.IPMIFailure, commands.parse_capabilities,
|
||||
raw_data)
|
||||
|
||||
def test_parse_version(self):
|
||||
raw_data = ['0x00', '0x00', '0x00', '0x05', '0x03', '0x07', '0x01',
|
||||
'0x02']
|
||||
expected = {'firmware': '1.2', 'ipmi': '3.0', 'nm': '3.0',
|
||||
'patch': '7'}
|
||||
result = commands.parse_version(raw_data)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_parse_version_invalid_lenght(self):
|
||||
raw_data = ['0x00', '0x00', '0x00', '0x05', '0x03', '0x07', '0x01']
|
||||
self.assertRaises(exception.IPMIFailure, commands.parse_version,
|
||||
raw_data)
|
||||
|
||||
def test_parse_version_conversion_error(self):
|
||||
raw_data = ['0x00', '0x00', '0x00', '0x05', '0x03', '0x07', '0x01',
|
||||
'boo']
|
||||
self.assertRaises(exception.IPMIFailure, commands.parse_version,
|
||||
raw_data)
|
||||
|
||||
|
||||
class ParsingFromFileTestCase(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ParsingFromFileTestCase, self).setUp()
|
||||
self.temp_file = tempfile.NamedTemporaryFile().name
|
||||
|
||||
def test_parsing_found(self):
|
||||
data = b'\x00\xFF\x00\xFF\x57\x01\x00\x0D\x01\x6A\xB2\x00\xFF'
|
||||
with open(self.temp_file, 'wb') as f:
|
||||
f.write(data)
|
||||
result = commands.parse_slave_and_channel(self.temp_file)
|
||||
self.assertEqual(('0x6a', '0x0b'), result)
|
||||
|
||||
def test_parsing_not_found(self):
|
||||
data = b'\x00\xFF\x00\xFF\x52\x01\x80\x0D\x01\x6A\xB7\x00\xFF'
|
||||
with open(self.temp_file, 'wb') as f:
|
||||
f.write(data)
|
||||
result = commands.parse_slave_and_channel(self.temp_file)
|
||||
self.assertIsNone(result)
|
354
ironic_staging_drivers/tests/unit/intel_nm/test_vendor.py
Normal file
354
ironic_staging_drivers/tests/unit/intel_nm/test_vendor.py
Normal file
@ -0,0 +1,354 @@
|
||||
# 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 Intel NM vendor interface
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.tests.unit.conductor import mgr_utils
|
||||
from ironic.tests.unit.db import base as db_base
|
||||
from ironic.tests.unit.objects import utils as obj_utils
|
||||
from ironic_lib import utils as ironic_utils
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
|
||||
from ironic_staging_drivers.intel_nm import ipmi
|
||||
from ironic_staging_drivers.intel_nm import nm_commands
|
||||
from ironic_staging_drivers.intel_nm import nm_vendor
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
_MAIN_IDS = {'domain_id': 'platform', 'policy_id': 111}
|
||||
|
||||
_POLICY = {'domain_id': 'platform', 'enable': True, 'policy_id': 111,
|
||||
'policy_trigger': 'none', 'action': 'alert',
|
||||
'power_domain': 'primary', 'target_limit': 100,
|
||||
'correction_time': 200, 'reporting_period': 600}
|
||||
|
||||
_SUSPEND = {'domain_id': 'platform', 'policy_id': 121,
|
||||
'periods': [{'start': 10, 'stop': 30, 'days': ['monday']}]}
|
||||
|
||||
_GET_CAP = {'domain_id': 'platform', 'policy_trigger': 'none',
|
||||
'power_domain': 'primary'}
|
||||