353 lines
14 KiB
Python
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)
|