summaryrefslogtreecommitdiff
path: root/ironic/drivers/modules/irmc
diff options
context:
space:
mode:
Diffstat (limited to 'ironic/drivers/modules/irmc')
-rw-r--r--ironic/drivers/modules/irmc/common.py226
-rw-r--r--ironic/drivers/modules/irmc/inspect.py98
-rw-r--r--ironic/drivers/modules/irmc/management.py289
-rw-r--r--ironic/drivers/modules/irmc/power.py64
-rw-r--r--ironic/drivers/modules/irmc/vendor.py75
5 files changed, 677 insertions, 75 deletions
diff --git a/ironic/drivers/modules/irmc/common.py b/ironic/drivers/modules/irmc/common.py
index 7a8fc0f1d..4341a82f4 100644
--- a/ironic/drivers/modules/irmc/common.py
+++ b/ironic/drivers/modules/irmc/common.py
@@ -15,9 +15,12 @@
"""
Common functionalities shared between different iRMC modules.
"""
+import json
import os
+import re
from oslo_log import log as logging
+from oslo_serialization import jsonutils
from oslo_utils import importutils
from oslo_utils import strutils
@@ -31,6 +34,23 @@ scci = importutils.try_import('scciclient.irmc.scci')
elcm = importutils.try_import('scciclient.irmc.elcm')
LOG = logging.getLogger(__name__)
+
+
+IRMC_OS_NAME_R = re.compile(r'iRMC\s+S\d+')
+IRMC_OS_NAME_NUM_R = re.compile(r'\d+$')
+IRMC_FW_VER_R = re.compile(r'\d(\.\d+)*\w*')
+IRMC_FW_VER_NUM_R = re.compile(r'\d(\.\d+)*')
+
+IPMI_ENABLED_BY_DEFAULT_RANGES = {
+ # iRMC S4 enables IPMI over LAN by default
+ '4': None,
+ # iRMC S5 enables IPMI over LAN by default
+ '5': None,
+ # iRMC S6 disables IPMI over LAN by default from version 2.00
+ '6': {'upper': '2.00'}}
+
+ELCM_STATUS_PATH = '/rest/v1/Oem/eLCM/eLCMStatus'
+
REQUIRED_PROPERTIES = {
'irmc_address': _("IP address or hostname of the iRMC. Required."),
'irmc_username': _("Username for the iRMC with administrator privileges. "
@@ -83,7 +103,9 @@ SNMP_V3_REQUIRED_PROPERTIES = {
SNMP_V3_OPTIONAL_PROPERTIES = {
'irmc_snmp_auth_proto': _("SNMPv3 message authentication protocol ID. "
"Required for version 'v3'. "
- "'sha' is supported."),
+ "If using iRMC S4/S5, only 'sha' is supported."
+ "If using iRMC S6, the valid options are "
+ "'sha256', 'sha384', 'sha512'."),
'irmc_snmp_priv_proto': _("SNMPv3 message privacy (encryption) protocol "
"ID. Required for version 'v3'. "
"'aes' is supported."),
@@ -243,7 +265,8 @@ def _parse_snmp_driver_info(node, info):
def _parse_snmp_v3_info(node, info):
snmp_info = {}
missing_info = []
- valid_values = {'irmc_snmp_auth_proto': ['sha'],
+ valid_values = {'irmc_snmp_auth_proto': ['sha', 'sha256', 'sha384',
+ 'sha512'],
'irmc_snmp_priv_proto': ['aes']}
valid_protocols = {'irmc_snmp_auth_proto': snmp.snmp_auth_protocols,
'irmc_snmp_priv_proto': snmp.snmp_priv_protocols}
@@ -433,3 +456,202 @@ def set_secure_boot_mode(node, enable):
raise exception.IRMCOperationError(
operation=_("setting secure boot mode"),
error=irmc_exception)
+
+
+def check_elcm_license(node):
+ """Connect to iRMC and return status of eLCM license
+
+ This function connects to iRMC REST API and check whether eLCM
+ license is active. This function can be used to check connection to
+ iRMC REST API.
+
+ :param node: An ironic node object
+ :returns: dictionary whose keys are 'active' and 'status_code'.
+ value of 'active' is boolean showing if eLCM license is active
+ and value of 'status_code' is int which is HTTP return code
+ from iRMC REST API access
+ :raises: InvalidParameterValue if invalid value is contained
+ in the 'driver_info' property.
+ :raises: MissingParameterValue if some mandatory key is missing
+ in the 'driver_info' property.
+ :raises: IRMCOperationError if the operation fails.
+ """
+ try:
+ d_info = parse_driver_info(node)
+ # GET to /rest/v1/Oem/eLCM/eLCMStatus returns
+ # JSON data like this:
+ #
+ # {
+ # "eLCMStatus":{
+ # "EnabledAndLicenced":"true",
+ # "SDCardMounted":"false"
+ # }
+ # }
+ #
+ # EnabledAndLicenced tells whether eLCM license is valid
+ #
+ r = elcm.elcm_request(d_info, 'GET', ELCM_STATUS_PATH)
+
+ # If r.status_code is 200, it means success and r.text is JSON.
+ # If it is 500, it means there is problem at iRMC side
+ # and iRMC cannot return eLCM status.
+ # If it was 401, elcm_request raises SCCIClientError.
+ # Otherwise, r.text may not be JSON.
+ if r.status_code == 200:
+ license_active = strutils.bool_from_string(
+ jsonutils.loads(r.text)['eLCMStatus']['EnabledAndLicenced'],
+ strict=True)
+ else:
+ license_active = False
+
+ return {'active': license_active, 'status_code': r.status_code}
+ except (scci.SCCIError,
+ json.JSONDecodeError,
+ TypeError,
+ KeyError,
+ ValueError) as irmc_exception:
+ LOG.error("Failed to check eLCM license status for node $(node)s",
+ {'node': node.uuid})
+ raise exception.IRMCOperationError(
+ operation='checking eLCM license status',
+ error=irmc_exception)
+
+
+def set_irmc_version(task):
+ """Fetch and save iRMC firmware version.
+
+ This function should be called before calling any other functions which
+ need to check node's iRMC firmware version.
+
+ Set `<iRMC OS>/<fw version>` to driver_internal_info['irmc_fw_version']
+
+ :param node: An ironic node object
+ :raises: InvalidParameterValue if invalid value is contained
+ in the 'driver_info' property.
+ :raises: MissingParameterValue if some mandatory key is missing
+ in the 'driver_info' property.
+ :raises: IRMCOperationError if the operation fails.
+ :raises: NodeLocked if the target node is already locked.
+ """
+
+ node = task.node
+ try:
+ report = get_irmc_report(node)
+ irmc_os, fw_version = scci.get_irmc_version_str(report)
+
+ fw_ver = node.driver_internal_info.get('irmc_fw_version')
+ if fw_ver != '/'.join([irmc_os, fw_version]):
+ task.upgrade_lock(purpose='saving firmware version')
+ node.set_driver_internal_info('irmc_fw_version',
+ f"{irmc_os}/{fw_version}")
+ node.save()
+ except scci.SCCIError as irmc_exception:
+ LOG.error("Failed to fetch iRMC FW version for node %s",
+ node.uuid)
+ raise exception.IRMCOperationError(
+ operation=_("fetching irmc fw version "),
+ error=irmc_exception)
+
+
+def _version_lt(v1, v2):
+ v1_l = v1.split('.')
+ v2_l = v2.split('.')
+ if len(v1_l) <= len(v2_l):
+ v1_l.extend(['0'] * (len(v2_l) - len(v1_l)))
+ else:
+ v2_l.extend(['0'] * (len(v1_l) - len(v2_l)))
+
+ for i in range(len(v1_l)):
+ if int(v1_l[i]) < int(v2_l[i]):
+ return True
+ elif int(v1_l[i]) > int(v2_l[i]):
+ return False
+ else:
+ return False
+
+
+def _version_le(v1, v2):
+ v1_l = v1.split('.')
+ v2_l = v2.split('.')
+ if len(v1_l) <= len(v2_l):
+ v1_l.extend(['0'] * (len(v2_l) - len(v1_l)))
+ else:
+ v2_l.extend(['0'] * (len(v1_l) - len(v2_l)))
+
+ for i in range(len(v1_l)):
+ if int(v1_l[i]) < int(v2_l[i]):
+ return True
+ elif int(v1_l[i]) > int(v2_l[i]):
+ return False
+ else:
+ return True
+
+
+def within_version_ranges(node, version_ranges):
+ """Read saved iRMC FW version and check if it is within the passed ranges.
+
+ :param node: An ironic node object
+ :param version_ranges: A Python dictionary containing version ranges in the
+ next format: <os_n>: <ranges>, where <os_n> is a string representing
+ iRMC OS number (e.g. '4') and <ranges> is a dictionaries indicating
+ the specific firmware version ranges under the iRMC OS number <os_n>.
+
+ The dictionary used in <ranges> only has two keys: 'min' and 'upper',
+ and value of each key is a string representing iRMC firmware version
+ number or None. Both keys can be absent and their value can be None.
+
+ It is acceptable to not set ranges for a <os_n> (for example set
+ <ranges> to None, {}, etc...), in this case, this function only
+ checks if the node's iRMC OS number matches the <os_n>.
+
+ Valid <version_ranges> example:
+ {'3': None, # all version of iRMC S3 matches
+ '4': {}, # all version of iRMC S4 matches
+ # all version of iRMC S5 matches
+ '5': {'min': None, 'upper': None},
+ # iRMC S6 whose version is >=1.20 matches
+ '6': {'min': '1.20', 'upper': None},
+ # iRMC S7 whose version is
+ # 5.51<= (version) <8.23 matches
+ '7': {'min': '5.51', 'upper': '8.23'}}
+
+ :returns: True if node's iRMC FW is in range, False if not or
+ fails to parse firmware version
+ """
+
+ try:
+ fw_version = node.driver_internal_info.get('irmc_fw_version', '')
+ irmc_os, irmc_ver = fw_version.split('/')
+
+ if IRMC_OS_NAME_R.match(irmc_os) and IRMC_FW_VER_R.match(irmc_ver):
+ os_num = IRMC_OS_NAME_NUM_R.search(irmc_os).group(0)
+ fw_num = IRMC_FW_VER_NUM_R.search(irmc_ver).group(0)
+
+ if os_num not in version_ranges:
+ return False
+
+ v_range = version_ranges[os_num]
+
+ # An OS number with no ranges setted means no need to check
+ # specific version, all the version under this OS number is valid.
+ if not v_range:
+ return True
+
+ # Specific range is setted, check if the node's
+ # firmware version is within it.
+ min_ver = v_range.get('min')
+ upper_ver = v_range.get('upper')
+ flag = True
+ if min_ver:
+ flag = _version_le(min_ver, fw_num)
+ if flag and upper_ver:
+ flag = _version_lt(fw_num, upper_ver)
+ return flag
+
+ except Exception:
+ # All exceptions are ignored
+ pass
+
+ LOG.warning('Failed to parse iRMC firmware version on node %(uuid)s: '
+ '%(fw_ver)s', {'uuid': node.uuid, 'fw_ver': fw_version})
+ return False
diff --git a/ironic/drivers/modules/irmc/inspect.py b/ironic/drivers/modules/irmc/inspect.py
index 9b6bff5bc..f7c2ad7ba 100644
--- a/ironic/drivers/modules/irmc/inspect.py
+++ b/ironic/drivers/modules/irmc/inspect.py
@@ -32,7 +32,7 @@ from ironic.drivers.modules.irmc import common as irmc_common
from ironic.drivers.modules import snmp
from ironic import objects
-scci = importutils.try_import('scciclient.irmc.scci')
+irmc = importutils.try_import('scciclient.irmc')
LOG = logging.getLogger(__name__)
@@ -122,6 +122,39 @@ def _get_mac_addresses(node):
if c == NODE_CLASS_OID_VALUE['primary']]
+def _get_capabilities_properties_without_ipmi(d_info, cap_props,
+ current_cap, props):
+ capabilities = {}
+ snmp_client = snmp.SNMPClient(
+ address=d_info['irmc_address'],
+ port=d_info['irmc_snmp_port'],
+ version=d_info['irmc_snmp_version'],
+ read_community=d_info['irmc_snmp_community'],
+ user=d_info.get('irmc_snmp_user'),
+ auth_proto=d_info.get('irmc_snmp_auth_proto'),
+ auth_key=d_info.get('irmc_snmp_auth_password'),
+ priv_proto=d_info.get('irmc_snmp_priv_proto'),
+ priv_key=d_info.get('irmc_snmp_priv_password'))
+
+ if 'rom_firmware_version' in cap_props:
+ capabilities['rom_firmware_version'] = \
+ irmc.snmp.get_bios_firmware_version(snmp_client)
+
+ if 'irmc_firmware_version' in cap_props:
+ capabilities['irmc_firmware_version'] = \
+ irmc.snmp.get_irmc_firmware_version(snmp_client)
+
+ if 'server_model' in cap_props:
+ capabilities['server_model'] = irmc.snmp.get_server_model(
+ snmp_client)
+
+ capabilities = utils.get_updated_capabilities(current_cap, capabilities)
+ if capabilities:
+ props['capabilities'] = capabilities
+
+ return props
+
+
def _inspect_hardware(node, existing_traits=None, **kwargs):
"""Inspect the node and get hardware information.
@@ -161,39 +194,50 @@ def _inspect_hardware(node, existing_traits=None, **kwargs):
try:
report = irmc_common.get_irmc_report(node)
- props = scci.get_essential_properties(
+ props = irmc.scci.get_essential_properties(
report, IRMCInspect.ESSENTIAL_PROPERTIES)
d_info = irmc_common.parse_driver_info(node)
- capabilities = scci.get_capabilities_properties(
- d_info,
- capabilities_props,
- gpu_ids,
- fpga_ids=fpga_ids,
- **kwargs)
- if capabilities:
- if capabilities.get('pci_gpu_devices') == 0:
- capabilities.pop('pci_gpu_devices')
-
- cpu_fpga = capabilities.pop('cpu_fpga', 0)
- if cpu_fpga == 0 and 'CUSTOM_CPU_FPGA' in new_traits:
- new_traits.remove('CUSTOM_CPU_FPGA')
- elif cpu_fpga != 0 and 'CUSTOM_CPU_FPGA' not in new_traits:
- new_traits.append('CUSTOM_CPU_FPGA')
-
- # Ironic no longer supports trusted boot
- capabilities.pop('trusted_boot', None)
- capabilities = utils.get_updated_capabilities(
- node.properties.get('capabilities'), capabilities)
+ if node.driver_internal_info.get('irmc_ipmi_succeed'):
+ capabilities = irmc.scci.get_capabilities_properties(
+ d_info,
+ capabilities_props,
+ gpu_ids,
+ fpga_ids=fpga_ids,
+ **kwargs)
if capabilities:
- props['capabilities'] = capabilities
+ if capabilities.get('pci_gpu_devices') == 0:
+ capabilities.pop('pci_gpu_devices')
+
+ cpu_fpga = capabilities.pop('cpu_fpga', 0)
+ if cpu_fpga == 0 and 'CUSTOM_CPU_FPGA' in new_traits:
+ new_traits.remove('CUSTOM_CPU_FPGA')
+ elif cpu_fpga != 0 and 'CUSTOM_CPU_FPGA' not in new_traits:
+ new_traits.append('CUSTOM_CPU_FPGA')
+
+ # Ironic no longer supports trusted boot
+ capabilities.pop('trusted_boot', None)
+ capabilities = utils.get_updated_capabilities(
+ node.properties.get('capabilities', ''), capabilities)
+ if capabilities:
+ props['capabilities'] = capabilities
+
+ else:
+ props = _get_capabilities_properties_without_ipmi(
+ d_info, capabilities_props,
+ node.properties.get('capabilities', ''), props)
macs = _get_mac_addresses(node)
- except (scci.SCCIInvalidInputError,
- scci.SCCIClientError,
+ except (irmc.scci.SCCIInvalidInputError,
+ irmc.scci.SCCIClientError,
exception.SNMPFailure) as e:
+ advice = ""
+ if ("SNMP operation" in str(e)):
+ advice = ("The SNMP related parameters' value may be different "
+ "with the server, please check if you have set them "
+ "correctly.")
error = (_("Inspection failed for node %(node_id)s "
- "with the following error: %(error)s") %
- {'node_id': node.uuid, 'error': e})
+ "with the following error: %(error)s. (advice)s") %
+ {'node_id': node.uuid, 'error': e, 'advice': advice})
raise exception.HardwareInspectionFailure(error=error)
return props, macs, new_traits
diff --git a/ironic/drivers/modules/irmc/management.py b/ironic/drivers/modules/irmc/management.py
index 079ae9e44..cf146f2cd 100644
--- a/ironic/drivers/modules/irmc/management.py
+++ b/ironic/drivers/modules/irmc/management.py
@@ -27,9 +27,10 @@ from ironic.conductor import task_manager
from ironic.conductor import utils as manager_utils
from ironic import conf
from ironic.drivers import base
+from ironic.drivers.modules import boot_mode_utils
from ironic.drivers.modules import ipmitool
from ironic.drivers.modules.irmc import common as irmc_common
-from ironic.drivers import utils as driver_utils
+from ironic.drivers.modules.redfish import management as redfish_management
irmc = importutils.try_import('scciclient.irmc')
@@ -204,7 +205,8 @@ def _restore_bios_config(task):
manager_utils.node_power_action(task, states.POWER_ON)
-class IRMCManagement(ipmitool.IPMIManagement):
+class IRMCManagement(ipmitool.IPMIManagement,
+ redfish_management.RedfishManagement):
def get_properties(self):
"""Return the properties of the interface.
@@ -224,9 +226,30 @@ class IRMCManagement(ipmitool.IPMIManagement):
: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)
+ if task.node.driver_internal_info.get('irmc_ipmi_succeed'):
+ irmc_common.parse_driver_info(task.node)
+ irmc_common.update_ipmi_properties(task)
+ super(IRMCManagement, self).validate(task)
+ else:
+ irmc_common.parse_driver_info(task.node)
+ super(ipmitool.IPMIManagement, self).validate(task)
+
+ def get_supported_boot_devices(self, task):
+ """Get list of supported boot devices
+
+ Actual code is delegated to IPMIManagement or RedfishManagement
+ based on iRMC firmware version.
+
+ :param task: A TaskManager instance
+ :returns: A list with the supported boot devices defined
+ in :mod:`ironic.common.boot_devices`.
+
+ """
+ if task.node.driver_internal_info.get('irmc_ipmi_succeed'):
+ return super(IRMCManagement, self).get_supported_boot_devices(task)
+ else:
+ return super(ipmitool.IPMIManagement,
+ self).get_supported_boot_devices(task)
@METRICS.timer('IRMCManagement.set_boot_device')
@task_manager.require_exclusive_lock
@@ -245,39 +268,112 @@ class IRMCManagement(ipmitool.IPMIManagement):
specified.
:raises: MissingParameterValue if a required parameter is missing.
:raises: IPMIFailure on an error from ipmitool.
+ :raises: RedfishConnectionError on Redfish operation failure.
+ :raises: RedfishError on Redfish operation failure.
+ """
+ if task.node.driver_internal_info.get('irmc_ipmi_succeed'):
+ if device not in self.get_supported_boot_devices(task):
+ raise exception.InvalidParameterValue(_(
+ "Invalid boot device %s specified.") % device)
+
+ uefi_mode = (
+ boot_mode_utils.get_boot_mode(task.node) == '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)
+ else:
+ if device not in self.get_supported_boot_devices(task):
+ raise exception.InvalidParameterValue(_(
+ "Invalid boot device %s specified. "
+ "Current iRMC firmware condition doesn't support IPMI "
+ "but Redfish.") % device)
+ super(ipmitool.IPMIManagement, self).set_boot_device(
+ task, device, persistent)
+
+ def get_boot_device(self, task):
+ """Get the current boot device for the task's node.
+ Returns the current boot device of the node.
+
+ :param task: a task from TaskManager.
+ :raises: InvalidParameterValue if an invalid boot device is
+ specified.
+ :raises: MissingParameterValue if a required parameter is missing.
+ :raises: IPMIFailure on an error from ipmitool.
+ :raises: RedfishConnectionError on Redfish operation failure.
+ :raises: RedfishError on Redfish operation failure.
+ :returns: a dictionary containing:
+
+ :boot_device: the boot device, one of
+ :mod:`ironic.common.boot_devices` or None if it is unknown.
+ :persistent: Whether the boot device will persist to all
+ future boots or not, None if it is unknown.
"""
- 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'
+ if task.node.driver_internal_info.get('irmc_ipmi_succeed'):
+ return super(IRMCManagement, self).get_boot_device(task)
else:
- data1 = '0xa0' if uefi_mode else '0x80'
- data2 = _BOOTPARAM5_DATA2[device]
+ return super(
+ ipmitool.IPMIManagement, self).get_boot_device(task)
- cmd8 = bootparam5 % (data1, data2)
- ipmitool.send_raw(task, cmd8)
+ def get_supported_boot_modes(self, task):
+ """Get a list of the supported boot modes.
+
+ IRMCManagement class doesn't support this method
+
+ :param task: a task from TaskManager.
+ :raises: UnsupportedDriverExtension if requested operation is
+ not supported by the driver
+ """
+ raise exception.UnsupportedDriverExtension(
+ driver=task.node.driver, extension='get_supported_boot_modes')
+
+ def set_boot_mode(self, task, mode):
+ """Set the boot mode for a node.
+
+ IRMCManagement class doesn't support this method
+
+ :param task: a task from TaskManager.
+ :param mode: The boot mode, one of
+ :mod:`ironic.common.boot_modes`.
+ :raises: UnsupportedDriverExtension if requested operation is
+ not supported by the driver
+ """
+ raise exception.UnsupportedDriverExtension(
+ driver=task.node.driver, extension='set_boot_mode')
+
+ def get_boot_mode(self, task):
+ """Get the current boot mode for a node.
+
+ IRMCManagement class doesn't support this method
+
+ :param task: a task from TaskManager.
+ :raises: UnsupportedDriverExtension if requested operation is
+ not supported by the driver
+ """
+ raise exception.UnsupportedDriverExtension(
+ driver=task.node.driver, extension='get_boot_mode')
@METRICS.timer('IRMCManagement.get_sensors_data')
def get_sensors_data(self, task):
@@ -330,7 +426,13 @@ class IRMCManagement(ipmitool.IPMIManagement):
if sensor_method == 'scci':
return _get_sensors_data(task)
elif sensor_method == 'ipmitool':
- return super(IRMCManagement, self).get_sensors_data(task)
+ if task.node.driver_internal_info.get('irmc_ipmi_succeed'):
+ return super(IRMCManagement, self).get_sensors_data(task)
+ else:
+ raise exception.InvalidParameterValue(_(
+ "Invalid sensor method %s specified. "
+ "IPMI operation doesn't work on current iRMC "
+ "condition.") % sensor_method)
@METRICS.timer('IRMCManagement.inject_nmi')
@task_manager.require_exclusive_lock
@@ -401,3 +503,120 @@ class IRMCManagement(ipmitool.IPMIManagement):
not supported by the driver or the hardware
"""
return irmc_common.set_secure_boot_mode(task.node, state)
+
+ def get_supported_indicators(self, task, component=None):
+ """Get a map of the supported indicators (e.g. LEDs).
+
+ IRMCManagement class doesn't support this method
+
+ :param task: a task from TaskManager.
+ :param component: If not `None`, return indicator information
+ for just this component, otherwise return indicators for
+ all existing components.
+ :raises: UnsupportedDriverExtension if requested operation is
+ not supported by the driver
+
+ """
+ raise exception.UnsupportedDriverExtension(
+ driver=task.node.driver, extension='get_supported_indicators')
+
+ def set_indicator_state(self, task, component, indicator, state):
+ """Set indicator on the hardware component to the desired state.
+
+ IRMCManagement class doesn't support this method
+
+ :param task: A task from TaskManager.
+ :param component: The hardware component, one of
+ :mod:`ironic.common.components`.
+ :param indicator: Indicator ID (as reported by
+ `get_supported_indicators`).
+ :state: Desired state of the indicator, one of
+ :mod:`ironic.common.indicator_states`.
+ :raises: UnsupportedDriverExtension if requested operation is
+ not supported by the driver
+ """
+ raise exception.UnsupportedDriverExtension(
+ driver=task.node.driver, extension='set_indicator_state')
+
+ def get_indicator_state(self, task, component, indicator):
+ """Get current state of the indicator of the hardware component.
+
+ IRMCManagement class doesn't support this method
+
+ :param task: A task from TaskManager.
+ :param component: The hardware component, one of
+ :mod:`ironic.common.components`.
+ :param indicator: Indicator ID (as reported by
+ `get_supported_indicators`).
+ :raises: UnsupportedDriverExtension if requested operation is
+ not supported by the driver
+ """
+ raise exception.UnsupportedDriverExtension(
+ driver=task.node.driver, extension='get_indicator_state')
+
+ def detect_vendor(self, task):
+ """Detects and returns the hardware vendor.
+
+ :param task: A task from TaskManager.
+ :raises: InvalidParameterValue if a required parameter is missing
+ :raises: MissingParameterValue if a required parameter is missing
+ :raises: RedfishError on Redfish operation error.
+ :raises: PasswordFileFailedToCreate from creating or writing to the
+ temporary file during IPMI operation.
+ :raises: processutils.ProcessExecutionError from executing ipmi command
+ :returns: String representing the BMC reported Vendor or
+ Manufacturer, otherwise returns None.
+ """
+ if task.node.driver_internal_info.get('irmc_ipmi_succeed'):
+ return super(IRMCManagement, self).detect_vendor(task)
+ else:
+ return super(ipmitool.IPMIManagement, self).detect_vendor(task)
+
+ def get_mac_addresses(self, task):
+ """Get MAC address information for the node.
+
+ IRMCManagement class doesn't support this method
+
+ :param task: A TaskManager instance containing the node to act on.
+ :raises: UnsupportedDriverExtension
+ """
+ raise exception.UnsupportedDriverExtension(
+ driver=task.node.driver, extension='get_mac_addresses')
+
+ @base.verify_step(priority=10)
+ def verify_http_https_connection_and_fw_version(self, task):
+ """Check http(s) connection to iRMC and save fw version
+
+ :param task' A task from TaskManager
+ 'raises: IRMCOperationError
+ """
+ error_msg_https = ('Access to REST API returns unexpected '
+ 'status code. Check driver_info parameter '
+ 'related to iRMC driver')
+ error_msg_http = ('Access to REST API returns unexpected '
+ 'status code. Check driver_info parameter '
+ 'or version of iRMC because iRMC does not '
+ 'support HTTP connection to iRMC REST API '
+ 'since iRMC S6 2.00.')
+ try:
+ # Check connection to iRMC
+ elcm_license = irmc_common.check_elcm_license(task.node)
+
+ # On iRMC S6 2.00, access to REST API through HTTP returns 404
+ if elcm_license.get('status_code') not in (200, 500):
+ port = task.node.driver_info.get(
+ 'irmc_port', CONF.irmc.get('port'))
+ if port == 80:
+ e_msg = error_msg_http
+ else:
+ e_msg = error_msg_https
+ raise exception.IRMCOperationError(
+ operation='establishing connection to REST API',
+ error=e_msg)
+
+ irmc_common.set_irmc_version(task)
+ except (exception.InvalidParameterValue,
+ exception.MissingParameterValue) as irmc_exception:
+ raise exception.IRMCOperationError(
+ operation='configuration validation',
+ error=irmc_exception)
diff --git a/ironic/drivers/modules/irmc/power.py b/ironic/drivers/modules/irmc/power.py
index 28041d835..48f7ea321 100644
--- a/ironic/drivers/modules/irmc/power.py
+++ b/ironic/drivers/modules/irmc/power.py
@@ -29,6 +29,7 @@ from ironic.drivers import base
from ironic.drivers.modules import ipmitool
from ironic.drivers.modules.irmc import boot as irmc_boot
from ironic.drivers.modules.irmc import common as irmc_common
+from ironic.drivers.modules.redfish import power as redfish_power
from ironic.drivers.modules import snmp
scci = importutils.try_import('scciclient.irmc.scci')
@@ -203,14 +204,17 @@ def _set_power_state(task, target_state, timeout=None):
_wait_power_state(task, states.SOFT_REBOOT, timeout=timeout)
except exception.SNMPFailure as snmp_exception:
+ advice = ("The SNMP related parameters' value may be different with "
+ "the server, please check if you have set them correctly.")
LOG.error("iRMC failed to acknowledge the target state "
- "for node %(node_id)s. Error: %(error)s",
- {'node_id': node.uuid, 'error': snmp_exception})
+ "for node %(node_id)s. Error: %(error)s. %(advice)s",
+ {'node_id': node.uuid, 'error': snmp_exception,
+ 'advice': advice})
raise exception.IRMCOperationError(operation=target_state,
error=snmp_exception)
-class IRMCPower(base.PowerInterface):
+class IRMCPower(redfish_power.RedfishPower, base.PowerInterface):
"""Interface for power-related actions."""
def get_properties(self):
@@ -233,7 +237,19 @@ class IRMCPower(base.PowerInterface):
is missing or invalid on the node.
:raises: MissingParameterValue if a required parameter is missing.
"""
- irmc_common.parse_driver_info(task.node)
+ # validate method of power interface is called at very first point
+ # in verifying.
+ # We take try-fallback approach against iRMC S6 2.00 and later
+ # incompatibility in which iRMC firmware disables IPMI by default.
+ # get_power_state method first try IPMI and if fails try Redfish
+ # along with setting irmc_ipmi_succeed flag to indicate if IPMI works.
+ if (task.node.driver_internal_info.get('irmc_ipmi_succeed')
+ or (task.node.driver_internal_info.get('irmc_ipmi_succeed')
+ is None)):
+ irmc_common.parse_driver_info(task.node)
+ else:
+ irmc_common.parse_driver_info(task.node)
+ super(IRMCPower, self).validate(task)
@METRICS.timer('IRMCPower.get_power_state')
def get_power_state(self, task):
@@ -241,14 +257,40 @@ class IRMCPower(base.PowerInterface):
:param task: a TaskManager instance containing the node to act on.
:returns: a power state. One of :mod:`ironic.common.states`.
- :raises: InvalidParameterValue if required ipmi parameters are missing.
- :raises: MissingParameterValue if a required parameter is missing.
- :raises: IPMIFailure on an error from ipmitool (from _power_status
- call).
+ :raises: InvalidParameterValue if required parameters are incorrect.
+ :raises: MissingParameterValue if required parameters are missing.
+ :raises: IRMCOperationError If IPMI or Redfish operation fails
"""
- irmc_common.update_ipmi_properties(task)
- ipmi_power = ipmitool.IPMIPower()
- return ipmi_power.get_power_state(task)
+ # If IPMI operation failed, iRMC may not enable/support IPMI,
+ # so fallback to Redfish.
+ # get_power_state is called at verifying and is called periodically
+ # so this method is good choice to determine IPMI enablement.
+ try:
+ irmc_common.update_ipmi_properties(task)
+ ipmi_power = ipmitool.IPMIPower()
+ pw_state = ipmi_power.get_power_state(task)
+ if (task.node.driver_internal_info.get('irmc_ipmi_succeed')
+ is not True):
+ task.upgrade_lock(purpose='update irmc_ipmi_succeed flag',
+ retry=True)
+ task.node.set_driver_internal_info('irmc_ipmi_succeed', True)
+ task.node.save()
+ task.downgrade_lock()
+ return pw_state
+ except exception.IPMIFailure:
+ if (task.node.driver_internal_info.get('irmc_ipmi_succeed')
+ is not False):
+ task.upgrade_lock(purpose='update irmc_ipmi_succeed flag',
+ retry=True)
+ task.node.set_driver_internal_info('irmc_ipmi_succeed', False)
+ task.node.save()
+ task.downgrade_lock()
+ try:
+ return super(IRMCPower, self).get_power_state(task)
+ except (exception.RedfishConnectionError,
+ exception.RedfishError):
+ raise exception.IRMCOperationError(
+ operation='IPMI try and Redfish fallback operation')
@METRICS.timer('IRMCPower.set_power_state')
@task_manager.require_exclusive_lock
diff --git a/ironic/drivers/modules/irmc/vendor.py b/ironic/drivers/modules/irmc/vendor.py
new file mode 100644
index 000000000..35535f69d
--- /dev/null
+++ b/ironic/drivers/modules/irmc/vendor.py
@@ -0,0 +1,75 @@
+# Copyright 2022 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.
+
+"""
+Vendor interface of iRMC driver
+"""
+
+from ironic.common import exception
+from ironic.common.i18n import _
+from ironic.drivers import base
+from ironic.drivers.modules.irmc import common as irmc_common
+
+
+class IRMCVendorPassthru(base.VendorInterface):
+ def get_properties(self):
+ """Return the properties of the interface.
+
+ :returns: Dictionary of <property name>:<property description> entries.
+ """
+ return irmc_common.COMMON_PROPERTIES
+
+ def validate(self, task, method=None, **kwargs):
+ """Validate vendor-specific actions.
+
+ This method validates whether the 'driver_info' property of the
+ supplied node contains the required information for this driver.
+
+ :param task: An instance of TaskManager.
+ :param method: Name of vendor passthru method
+ :raises: InvalidParameterValue if invalid value is contained
+ in the 'driver_info' property.
+ :raises: MissingParameterValue if some mandatory key is missing
+ in the 'driver_info' property.
+ """
+ irmc_common.parse_driver_info(task.node)
+
+ @base.passthru(['POST'],
+ async_call=True,
+ description='Connect to iRMC and fetch iRMC firmware '
+ 'version and, if firmware version has not been cached '
+ 'in or actual firmware version is different from one in '
+ 'driver_internal_info/irmc_fw_version, store firmware '
+ 'version in driver_internal_info/irmc_fw_version.',
+ attach=False,
+ require_exclusive_lock=False)
+ def cache_irmc_firmware_version(self, task, **kwargs):
+ """Fetch and save iRMC firmware version.
+
+ This method connects to iRMC and fetch iRMC firmware verison.
+ If fetched firmware version is not cached in or is different from
+ one in driver_internal_info/irmc_fw_version, store fetched version
+ in driver_internal_info/irmc_fw_version.
+
+ :param task: An instance of TaskManager.
+ :raises: IRMCOperationError if some error occurs
+ """
+ try:
+ irmc_common.set_irmc_version(task)
+ except (exception.IRMCOperationError,
+ exception.InvalidParameterValue,
+ exception.MissingParameterValue,
+ exception.NodeLocked) as e:
+ raise exception.IRMCOperationError(
+ operation=_('caching firmware version'), error=e)