ironic-staging-drivers/ironic_staging_drivers/intel_nm/nm_vendor.py

353 lines
14 KiB
Python

# 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.drivers.modules import ipmitool
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
from ironic_staging_drivers.common.i18n import _
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', 'statistics_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('Start detection of Intel Node Manager on node %s',
node.uuid)
sdr_filename = os.path.join(CONF.tempdir, node.uuid + '.sdr')
res = None
try:
ipmitool.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:
ipmitool.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('Can not obtain Intel Node Manager address for '
'node %(node)s: %(err)s',
{'node': task.node.uuid, 'err': 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 = ipmitool.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('Error in returned data for node %(node)s: '
'%(err)s', {'node': task.node.uuid,
'err': 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 _validate_policy_methods(self, method, **kwargs):
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)
if kwargs['scope'] != 'global' and 'domain_id' not in kwargs:
raise exception.MissingParameterValue(_('Missing "domain_id"'))
if kwargs['scope'] == 'policy' and 'policy_id' not in kwargs:
raise exception.MissingParameterValue(_('Missing "policy_id"'))
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 'correction_time' not in kwargs:
raise exception.MissingParameterValue(
_('Missing "correction_time" for no-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)
def _validate_statistics_methods(self, method, **kwargs):
jsonschema.validate(kwargs, self.statistics_schema)
global_params = ('unhandled_requests', 'response_time',
'cpu_throttling', 'memory_throttling',
'communication_failures')
if kwargs['scope'] == 'policy' and 'policy_id' not in kwargs:
raise exception.MissingParameterValue(_('Missing "policy_id"'))
if kwargs.get('parameter_name') not in global_params:
if 'domain_id' not in kwargs:
raise exception.MissingParameterValue(_('Missing "domain_id"'))
if method == 'reset_nm_statistics':
if 'parameter_name' in kwargs:
if kwargs['parameter_name'] not in global_params:
raise exception.InvalidParameterValue(
_('Invalid parameter name for resetting statistic, '
'individual reset is possible only for: %s') %
', '.join(global_params))
elif method == 'get_nm_statistics':
if 'parameter_name' not in kwargs:
raise exception.MissingParameterValue(
_('Parameter name is mandatory for getting statistics'))
# valid parameters depend on scope
if (kwargs['parameter_name'] not in
nm_commands.STATISTICS[kwargs['scope']]):
raise exception.InvalidParameterValue(
_('Invalid parameter name %(param)s for scope '
'%(scope)s') % {'param': kwargs['parameter_name'],
'scope': kwargs['scope']})
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 'statistics' in method:
self._validate_statistics_methods(method, **kwargs)
else:
self._validate_policy_methods(method, **kwargs)
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_call=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_call=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_call=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_call=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)
@base.passthru(['GET'], async_call=False)
def get_nm_statistics(self, task, **kwargs):
"""Get Intel Node Manager statistics.
:param task: a TaskManager instance.
:param kwargs: data passed to method.
:raises: IPMIFailure on an error.
:returns: a dictionary containing statistics info.
"""
return _execute_nm_command(task, kwargs,
nm_commands.get_statistics,
nm_commands.parse_statistics)
@base.passthru(['PUT'])
def reset_nm_statistics(self, task, **kwargs):
"""Reset Intel Node Manager statistics.
:param task: a TaskManager instance.
:param kwargs: data passed to method.
:raises: IPMIFailure on an error.
"""
_execute_nm_command(task, kwargs, nm_commands.reset_statistics)