c46b720036
This patch enhances iRMC management driver to support Inject NMI. Partial-Bug: #1526226 Change-Id: Id9dde5094239f215297d350a78b005accbdd8be4
259 lines
10 KiB
Python
259 lines
10 KiB
Python
# Copyright 2015 FUJITSU LIMITED
|
|
#
|
|
# 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.
|
|
"""
|
|
iRMC Management Driver
|
|
"""
|
|
from ironic_lib import metrics_utils
|
|
from oslo_log import log as logging
|
|
from oslo_utils import importutils
|
|
|
|
from ironic.common import boot_devices
|
|
from ironic.common import exception
|
|
from ironic.common.i18n import _, _LE
|
|
from ironic.conductor import task_manager
|
|
from ironic.drivers.modules import ipmitool
|
|
from ironic.drivers.modules.irmc import common as irmc_common
|
|
from ironic.drivers import utils as driver_utils
|
|
|
|
scci = importutils.try_import('scciclient.irmc.scci')
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
METRICS = metrics_utils.get_metrics_logger(__name__)
|
|
|
|
# Boot Option Parameters #5 Data2 defined in
|
|
# Set/Get System Boot Options Command, IPMI spec v2.0.
|
|
_BOOTPARAM5_DATA2 = {boot_devices.PXE: '0x04',
|
|
boot_devices.DISK: '0x08',
|
|
# note (naohirot)
|
|
# boot_devices.CDROM is tentatively set to '0x20' rather
|
|
# than '0x14' as a work-around to force iRMC vmedia boot.
|
|
# 0x14 = Force boot from default CD/DVD
|
|
# 0x20 = Force boot from remotely connected CD/DVD
|
|
boot_devices.CDROM: '0x20',
|
|
boot_devices.BIOS: '0x18',
|
|
boot_devices.SAFE: '0x0c',
|
|
}
|
|
|
|
|
|
def _get_sensors_data(task):
|
|
"""Get sensors data method.
|
|
|
|
It gets sensor data from the task's node via SCCI, and convert the data
|
|
from XML to the dict format.
|
|
|
|
:param task: A TaskManager instance.
|
|
:raises: FailedToGetSensorData when getting the sensor data fails.
|
|
:returns: Returns a consistent formatted dict of sensor data grouped
|
|
by sensor type, which can be processed by Ceilometer.
|
|
"""
|
|
|
|
try:
|
|
report = irmc_common.get_irmc_report(task.node)
|
|
sensor = scci.get_sensor_data(report)
|
|
|
|
except (exception.InvalidParameterValue,
|
|
exception.MissingParameterValue,
|
|
scci.SCCIInvalidInputError,
|
|
scci.SCCIClientError) as e:
|
|
LOG.error(_LE("SCCI get sensor data failed for node %(node_id)s "
|
|
"with the following error: %(error)s"),
|
|
{'node_id': task.node.uuid, 'error': e})
|
|
raise exception.FailedToGetSensorData(
|
|
node=task.node.uuid, error=e)
|
|
|
|
sensors_data = {}
|
|
for sdr in sensor:
|
|
sensor_type_name = sdr.find('./Data/Decoded/Sensor/TypeName')
|
|
sensor_type_number = sdr.find('./Data/Decoded/Sensor/Type')
|
|
entity_name = sdr.find('./Data/Decoded/Entity/Name')
|
|
entity_id = sdr.find('./Data/Decoded/Entity/ID')
|
|
|
|
if None in (sensor_type_name, sensor_type_number,
|
|
entity_name, entity_id):
|
|
continue
|
|
|
|
sensor_type = ('%s (%s)' %
|
|
(sensor_type_name.text, sensor_type_number.text))
|
|
sensor_id = ('%s (%s)' %
|
|
(entity_name.text, entity_id.text))
|
|
reading_value = sdr.find(
|
|
'./Data/Decoded/Sensor/Thresholds/*/Normalized')
|
|
reading_value_text = "None" if (
|
|
reading_value is None) else str(reading_value.text)
|
|
reading_units = sdr.find('./Data/Decoded/Sensor/BaseUnitName')
|
|
reading_units_text = "None" if (
|
|
reading_units is None) else str(reading_units.text)
|
|
sensor_reading = '%s %s' % (reading_value_text, reading_units_text)
|
|
|
|
sensors_data.setdefault(sensor_type, {})[sensor_id] = {
|
|
'Sensor Reading': sensor_reading,
|
|
'Sensor ID': sensor_id,
|
|
'Units': reading_units_text,
|
|
}
|
|
|
|
return sensors_data
|
|
|
|
|
|
class IRMCManagement(ipmitool.IPMIManagement):
|
|
|
|
def get_properties(self):
|
|
"""Return the properties of the interface.
|
|
|
|
:returns: Dictionary of <property name>:<property description> entries.
|
|
"""
|
|
return irmc_common.COMMON_PROPERTIES
|
|
|
|
@METRICS.timer('IRMCManagement.validate')
|
|
def validate(self, task):
|
|
"""Validate the driver-specific management information.
|
|
|
|
This method validates whether the 'driver_info' property of the
|
|
supplied node contains the required information for this driver.
|
|
|
|
:param task: A TaskManager instance containing the node to act on.
|
|
:raises: InvalidParameterValue if required parameters are invalid.
|
|
:raises: MissingParameterValue if a required parameter is missing.
|
|
"""
|
|
irmc_common.parse_driver_info(task.node)
|
|
irmc_common.update_ipmi_properties(task)
|
|
super(IRMCManagement, self).validate(task)
|
|
|
|
@METRICS.timer('IRMCManagement.set_boot_device')
|
|
@task_manager.require_exclusive_lock
|
|
def set_boot_device(self, task, device, persistent=False):
|
|
"""Set the boot device for a node.
|
|
|
|
Set the boot device to use on next reboot of the node.
|
|
|
|
:param task: A task from TaskManager.
|
|
:param device: The boot device, one of the supported devices
|
|
listed in :mod:`ironic.common.boot_devices`.
|
|
:param persistent: Boolean value. True if the boot device will
|
|
persist to all future boots, False if not.
|
|
Default: False.
|
|
:raises: InvalidParameterValue if an invalid boot device is
|
|
specified.
|
|
:raises: MissingParameterValue if a required parameter is missing.
|
|
:raises: IPMIFailure on an error from ipmitool.
|
|
|
|
"""
|
|
if device not in self.get_supported_boot_devices(task):
|
|
raise exception.InvalidParameterValue(_(
|
|
"Invalid boot device %s specified.") % device)
|
|
|
|
uefi_mode = (
|
|
driver_utils.get_node_capability(task.node, 'boot_mode') == 'uefi')
|
|
|
|
# disable 60 secs timer
|
|
timeout_disable = "0x00 0x08 0x03 0x08"
|
|
ipmitool.send_raw(task, timeout_disable)
|
|
|
|
# note(naohirot):
|
|
# Set System Boot Options : ipmi cmd '0x08', bootparam '0x05'
|
|
#
|
|
# $ ipmitool raw 0x00 0x08 0x05 data1 data2 0x00 0x00 0x00
|
|
#
|
|
# data1 : '0xe0' persistent + uefi
|
|
# '0xc0' persistent + bios
|
|
# '0xa0' next only + uefi
|
|
# '0x80' next only + bios
|
|
# data2 : boot device defined in the dict _BOOTPARAM5_DATA2
|
|
|
|
bootparam5 = '0x00 0x08 0x05 %s %s 0x00 0x00 0x00'
|
|
if persistent:
|
|
data1 = '0xe0' if uefi_mode else '0xc0'
|
|
else:
|
|
data1 = '0xa0' if uefi_mode else '0x80'
|
|
data2 = _BOOTPARAM5_DATA2[device]
|
|
|
|
cmd8 = bootparam5 % (data1, data2)
|
|
ipmitool.send_raw(task, cmd8)
|
|
|
|
@METRICS.timer('IRMCManagement.get_sensors_data')
|
|
def get_sensors_data(self, task):
|
|
"""Get sensors data method.
|
|
|
|
It gets sensor data from the task's node via SCCI, and convert the data
|
|
from XML to the dict format.
|
|
|
|
:param task: A TaskManager instance.
|
|
:raises: FailedToGetSensorData when getting the sensor data fails.
|
|
:raises: FailedToParseSensorData when parsing sensor data fails.
|
|
:raises: InvalidParameterValue if required parameters are invalid.
|
|
:raises: MissingParameterValue if a required parameter is missing.
|
|
:returns: Returns a consistent formatted dict of sensor data grouped
|
|
by sensor type, which can be processed by Ceilometer.
|
|
Example::
|
|
|
|
{
|
|
'Sensor Type 1': {
|
|
'Sensor ID 1': {
|
|
'Sensor Reading': 'Value1 Units1',
|
|
'Sensor ID': 'Sensor ID 1',
|
|
'Units': 'Units1'
|
|
},
|
|
'Sensor ID 2': {
|
|
'Sensor Reading': 'Value2 Units2',
|
|
'Sensor ID': 'Sensor ID 2',
|
|
'Units': 'Units2'
|
|
}
|
|
},
|
|
'Sensor Type 2': {
|
|
'Sensor ID 3': {
|
|
'Sensor Reading': 'Value3 Units3',
|
|
'Sensor ID': 'Sensor ID 3',
|
|
'Units': 'Units3'
|
|
},
|
|
'Sensor ID 4': {
|
|
'Sensor Reading': 'Value4 Units4',
|
|
'Sensor ID': 'Sensor ID 4',
|
|
'Units': 'Units4'
|
|
}
|
|
}
|
|
}
|
|
|
|
"""
|
|
# irmc_common.parse_driver_info() makes sure that
|
|
# d_info['irmc_sensor_method'] is either 'scci' or 'ipmitool'.
|
|
d_info = irmc_common.parse_driver_info(task.node)
|
|
sensor_method = d_info['irmc_sensor_method']
|
|
if sensor_method == 'scci':
|
|
return _get_sensors_data(task)
|
|
elif sensor_method == 'ipmitool':
|
|
return super(IRMCManagement, self).get_sensors_data(task)
|
|
|
|
@METRICS.timer('IRMCManagement.inject_nmi')
|
|
@task_manager.require_exclusive_lock
|
|
def inject_nmi(self, task):
|
|
"""Inject NMI, Non Maskable Interrupt.
|
|
|
|
Inject NMI (Non Maskable Interrupt) for a node immediately.
|
|
|
|
:param task: A TaskManager instance containing the node to act on.
|
|
:raises: IRMCOperationError on an error from SCCI
|
|
:returns: None
|
|
|
|
"""
|
|
node = task.node
|
|
irmc_client = irmc_common.get_irmc_client(node)
|
|
try:
|
|
irmc_client(scci.POWER_RAISE_NMI)
|
|
except scci.SCCIClientError as err:
|
|
LOG.error(
|
|
_LE('iRMC Inject NMI failed for node %(node)s: %(err)s.'),
|
|
{'node': node.uuid, 'err': err})
|
|
raise exception.IRMCOperationError(
|
|
operation=scci.POWER_RAISE_NMI, error=err)
|