Implement iRMC BIOS configuration

This patch supports BIOS configuration for iRMC drivers using
out-of-band method.

Tested successfully on TX2540 M1 along with eLCM license.

Story: #1743674
Task: #10651
Co-Authored-By: Nguyen Van Trung <trungnv@vn.fujitsu.com>
Change-Id: I61f15e7c65c4ef5cc5f959d2b016b053e70ba19b
This commit is contained in:
Luong Anh Tuan 2018-01-17 09:18:45 +07:00 committed by Nguyen Van Trung
parent e5068965f6
commit fa59565be0
9 changed files with 401 additions and 3 deletions

View File

@ -18,7 +18,7 @@ Prerequisites
* Install `python-scciclient <https://pypi.org/project/python-scciclient>`_
and `pysnmp <https://pypi.org/project/pysnmp>`_ packages::
$ pip install "python-scciclient>=0.7.0" pysnmp
$ pip install "python-scciclient>=0.7.1" pysnmp
Hardware Type
=============
@ -33,6 +33,10 @@ Hardware interfaces
The ``irmc`` hardware type overrides the selection of the following
hardware interfaces:
* bios
Supports ``irmc`` and ``no-bios``.
The default is ``irmc``.
* boot
Supports ``irmc-virtual-media``, ``irmc-pxe``, and ``pxe``.
The default is ``irmc-virtual-media``. The ``irmc-virtual-media`` boot
@ -81,6 +85,7 @@ interfaces enabled for ``irmc`` hardware type.
[DEFAULT]
enabled_hardware_types = irmc
enabled_bios_interfaces = irmc
enabled_boot_interfaces = irmc-virtual-media,irmc-pxe
enabled_console_interfaces = ipmitool-socat,ipmitool-shellinabox,no-console
enabled_deploy_interfaces = iscsi,direct
@ -97,6 +102,7 @@ Here is a command example to enroll a node with ``irmc`` hardware type.
.. code-block:: console
openstack baremetal node create --os-baremetal-api-version=1.31 \
--bios-interface irmc \
--boot-interface irmc-pxe \
--deploy-interface direct \
--inspect-interface irmc \
@ -481,6 +487,64 @@ The RAID configuration is supported as a manual cleaning step.
See :ref:`raid` for more details and examples.
BIOS configuration Support
^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``irmc`` hardware type provides the iRMC BIOS configuration with ``irmc``
bios interface.
.. warning::
``irmc`` bios interface does not support``factory_reset``.
Configuration
~~~~~~~~~~~~~
The BIOS configuration in the iRMC driver supports the following settings:
- ``boot_option_filter``: Specifies from which drives can be booted. This
supports following options: ``UefiAndLegacy``, ``LegacyOnly``, ``UefiOnly``.
- ``check_controllers_health_status_enabled``: The UEFI FW checks the
controller health status. This supports following options: ``true``, ``false``.
- ``cpu_active_processor_cores``: The number of active processor cores 1...n.
Option 0 indicates that all available processor cores are active.
- ``cpu_adjacent_cache_line_prefetch_enabled``: The processor loads the requested
cache line and the adjacent cache line. This supports following options:
``true``, ``false``.
- ``cpu_vt_enabled``: Supports the virtualization of platform hardware and
several software environments, based on Virtual Machine Extensions to
support the use of several software environments using virtual computers.
This supports following options: ``true``, ``false``.
- ``flash_write_enabled``: The system BIOS can be written. Flash BIOS update
is possible. This supports following options: ``true``, ``false``.
- ``hyper_threading_enabled``: Hyper-threading technology allows a single
physical processor core to appear as several logical processors. This
supports following options: ``true``, ``false``.
- ``keep_void_boot_options_enabled``: Boot Options will not be removed from
"Boot Option Priority" list. This supports following options: ``true``,
``false``.
- ``launch_csm_enabled``: Specifies whether the Compatibility Support Module
(CSM) is executed. This supports following options: ``true``, ``false``.
- ``os_energy_performance_override_enabled``: Prevents the OS from overruling
any energy efficiency policy setting of the setup. This supports following
options: ``true``, ``false``.
- ``pci_aspm_support``: Active State Power Management (ASPM) is used to
power-manage the PCI Express links, thus consuming less power. This
supports following options: ``Disabled``, ``Auto``, ``L0Limited``,
``L1only``, ``L0Force``.
- ``pci_above_4g_decoding_enabled``: Specifies if memory resources above the
4GB address boundary can be assigned to PCI devices. This supports
following options: ``true``, ``false``.
- ``power_on_source``: Specifies whether the switch on sources for the system
are managed by the BIOS or the ACPI operating system. This supports
following options: ``BiosControlled``, ``AcpiControlled``.
- ``single_root_io_virtualization_support_enabled``: Single Root IO
Virtualization Support is active. This supports following
options: ``true``, ``false``.
The BIOS configuration is supported as a manual cleaning step. See :ref:`bios`
for more details and examples.
Supported platforms
===================
This driver supports FUJITSU PRIMERGY BX S4 or RX S8 servers and above.

View File

@ -8,7 +8,7 @@ proliantutils>=2.5.0
pysnmp
python-ironic-inspector-client>=1.5.0
python-oneviewclient<3.0.0,>=2.5.2
python-scciclient>=0.7.0
python-scciclient>=0.7.1
python-ilorest-library>=2.1.0
hpOneView>=4.4.0
UcsSdk==0.8.2.2

View File

@ -20,6 +20,7 @@ from ironic.drivers import generic
from ironic.drivers.modules import agent
from ironic.drivers.modules import inspector
from ironic.drivers.modules import ipmitool
from ironic.drivers.modules.irmc import bios
from ironic.drivers.modules.irmc import boot
from ironic.drivers.modules.irmc import inspect
from ironic.drivers.modules.irmc import management
@ -36,6 +37,11 @@ class IRMCHardware(generic.GenericHardware):
have iRMC S4 management system.
"""
@property
def supported_bios_interfaces(self):
"""List of supported bios interfaces."""
return [bios.IRMCBIOS, noop.NoBIOS]
@property
def supported_boot_interfaces(self):
"""List of supported boot interfaces."""

View File

@ -0,0 +1,140 @@
# Copyright 2018 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 BIOS configuration specific methods
"""
from ironic_lib import metrics_utils
from oslo_log import log as logging
from oslo_utils import importutils
from ironic.common import exception
from ironic.drivers import base
from ironic.drivers.modules.irmc import common as irmc_common
from ironic import objects
irmc = importutils.try_import('scciclient.irmc')
LOG = logging.getLogger(__name__)
METRICS = metrics_utils.get_metrics_logger(__name__)
class IRMCBIOS(base.BIOSInterface):
def get_properties(self):
"""Return the properties of the interface."""
return irmc_common.COMMON_PROPERTIES
@METRICS.timer('IRMCBIOS.validate')
def validate(self, task):
"""Validate the driver-specific Node info.
This method validates whether the 'driver_info' property of the
supplied node contains the required information for this driver to
manage the BIOS settings of the node.
:param task: a TaskManager instance containing the node to act on.
:raises: InvalidParameterValue if required driver_info attribute
is missing or invalid on the node.
:raises: MissingParameterValue if a required parameter is missing
in the driver_info property.
"""
irmc_common.parse_driver_info(task.node)
@METRICS.timer('IRMCBIOS.apply_configuration')
@base.clean_step(priority=0, abortable=False, argsinfo={
'settings': {
'description': "Dictionary containing the BIOS configuration.",
'required': True
}
})
def apply_configuration(self, task, settings):
"""Applies BIOS configuration on the given node.
This method takes the BIOS settings from the settings param and
applies BIOS configuration on the given node.
After the BIOS configuration is done, self.cache_bios_settings() may
be called to sync the node's BIOS-related information with the BIOS
configuration applied on the node.
It will also validate the given settings before applying any
settings and manage failures when setting an invalid BIOS config.
In the case of needing password to update the BIOS config, it will be
taken from the driver_info properties.
:param task: a TaskManager instance.
:param settings: Dictionary containing the BIOS configuration. It
may be an empty dictionary as well.
:raises: IRMCOperationError,if apply bios settings failed.
"""
irmc_info = irmc_common.parse_driver_info(task.node)
try:
LOG.info('Apply BIOS configuration for node %(node_uuid)s: '
'%(settings)s', {'settings': settings,
'node_uuid': task.node.uuid})
irmc.elcm.set_bios_configuration(irmc_info, settings)
except irmc.scci.SCCIError as e:
LOG.error('Failed to apply BIOS configuration on node '
'%(node_uuid)s. Error: %(error)s',
{'node_uuid': task.node.uuid, 'error': e})
raise exception.IRMCOperationError(
operation='Apply BIOS configuration', error=e)
@METRICS.timer('IRMCBIOS.factory_reset')
def factory_reset(self, task):
"""Reset BIOS configuration to factory default on the given node.
:param task: a TaskManager instance.
:raises: UnsupportedDriverExtension, if the node's driver doesn't
support BIOS reset.
"""
raise exception.UnsupportedDriverExtension(
driver=task.node.driver, extension='factory_reset')
@METRICS.timer('IRMCBIOS.cache_bios_settings')
def cache_bios_settings(self, task):
"""Store or update BIOS settings on the given node.
This method stores BIOS properties to the bios settings db
:param task: a TaskManager instance.
:raises: IRMCOperationError,if get bios settings failed.
:returns: None if it is complete.
"""
irmc_info = irmc_common.parse_driver_info(task.node)
node_id = task.node.id
try:
settings = irmc.elcm.get_bios_settings(irmc_info)
except irmc.scci.SCCIError as e:
LOG.error('Failed to retrieve the current BIOS settings for node '
'%(node)s. Error: %(error)s', {'node': task.node.uuid,
'error': e})
raise exception.IRMCOperationError(operation='Cache BIOS settings',
error=e)
create_list, update_list, delete_list, nochange_list = (
objects.BIOSSettingList.sync_node_setting(task.context, node_id,
settings))
if len(create_list) > 0:
objects.BIOSSettingList.create(task.context, node_id, create_list)
if len(update_list) > 0:
objects.BIOSSettingList.save(task.context, node_id, update_list)
if len(delete_list) > 0:
delete_names = [setting['name'] for setting in delete_list]
objects.BIOSSettingList.delete(task.context, node_id,
delete_names)

View File

@ -0,0 +1,152 @@
# Copyright 2018 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.
"""
Test class for IRMC BIOS configuration
"""
import mock
from ironic.common import exception
from ironic.conductor import task_manager
from ironic.drivers.modules.irmc import bios as irmc_bios
from ironic.drivers.modules.irmc import common as irmc_common
from ironic import objects
from ironic.tests.unit.drivers.modules.irmc import test_common
class IRMCBIOSTestCase(test_common.BaseIRMCTest):
def setUp(self):
super(IRMCBIOSTestCase, self).setUp()
self.config(enabled_bios_interfaces=['irmc'])
@mock.patch.object(irmc_common, 'parse_driver_info',
autospec=True)
def test_validate(self, parse_driver_info_mock):
with task_manager.acquire(self.context, self.node.uuid) as task:
task.driver.bios.validate(task)
parse_driver_info_mock.assert_called_once_with(task.node)
@mock.patch.object(irmc_bios.irmc.elcm, 'set_bios_configuration',
autospec=True)
def test_apply_configuration(self, set_bios_configuration_mock):
settings = [{
"name": "launch_csm_enabled",
"value": True
}, {
"name": "hyper_threading_enabled",
"value": True
}, {
"name": "cpu_vt_enabled",
"value": True
}]
with task_manager.acquire(self.context, self.node.uuid) as task:
irmc_info = irmc_common.parse_driver_info(task.node)
task.driver.bios.apply_configuration(task, settings)
set_bios_configuration_mock.assert_called_once_with(irmc_info,
settings)
@mock.patch.object(irmc_bios.irmc.elcm, 'set_bios_configuration',
autospec=True)
def test_apply_configuration_failed(self, set_bios_configuration_mock):
settings = [{
"name": "launch_csm_enabled",
"value": True
}, {
"name": "hyper_threading_enabled",
"value": True
}, {
"name": "setting",
"value": True
}]
irmc_bios.irmc.scci.SCCIError = Exception
set_bios_configuration_mock.side_effect = Exception
with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(exception.IRMCOperationError,
task.driver.bios.apply_configuration,
task, settings)
def test_factory_reset(self):
with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(exception.UnsupportedDriverExtension,
task.driver.bios.factory_reset, task)
@mock.patch.object(objects.BIOSSettingList, 'sync_node_setting')
@mock.patch.object(objects.BIOSSettingList, 'create')
@mock.patch.object(objects.BIOSSettingList, 'save')
@mock.patch.object(objects.BIOSSettingList, 'delete')
@mock.patch.object(irmc_bios.irmc.elcm, 'get_bios_settings',
autospec=True)
def test_cache_bios_settings(self, get_bios_settings_mock,
delete_mock, save_mock, create_mock,
sync_node_setting_mock):
settings = [{
"name": "launch_csm_enabled",
"value": True
}, {
"name": "hyper_threading_enabled",
"value": True
}, {
"name": "cpu_vt_enabled",
"value": True
}]
with task_manager.acquire(self.context, self.node.uuid) as task:
irmc_info = irmc_common.parse_driver_info(task.node)
get_bios_settings_mock.return_value = settings
sync_node_setting_mock.return_value = \
(
[
{
"name": "launch_csm_enabled",
"value": True
}],
[
{
"name": "hyper_threading_enabled",
"value": True
}],
[
{
"name": "cpu_vt_enabled",
"value": True
}],
[]
)
task.driver.bios.cache_bios_settings(task)
get_bios_settings_mock.assert_called_once_with(irmc_info)
sync_node_setting_mock.assert_called_once_with(task.context,
task.node.id,
settings)
create_mock.assert_called_once_with(
task.context, task.node.id,
sync_node_setting_mock.return_value[0])
save_mock.assert_called_once_with(
task.context, task.node.id,
sync_node_setting_mock.return_value[1])
delete_names = \
[setting['name'] for setting in
sync_node_setting_mock.return_value[2]]
delete_mock.assert_called_once_with(task.context, task.node.id,
delete_names)
@mock.patch.object(irmc_bios.irmc.elcm, 'get_bios_settings',
autospec=True)
def test_cache_bios_settings_failed(self, get_bios_settings_mock):
irmc_bios.irmc.scci.SCCIError = Exception
get_bios_settings_mock.side_effect = Exception
with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(exception.IRMCOperationError,
task.driver.bios.cache_bios_settings,
task)

View File

@ -39,6 +39,7 @@ class BaseIRMCTest(db_base.DbTestCase):
self.config(enabled_hardware_types=['irmc', 'fake-hardware'],
enabled_power_interfaces=['irmc', 'fake'],
enabled_management_interfaces=['irmc', 'fake'],
enabled_bios_interfaces=['irmc', 'no-bios', 'fake'],
enabled_boot_interfaces=[self.boot_interface, 'fake'],
enabled_inspect_interfaces=['irmc', 'no-inspect', 'fake'])
self.info = db_utils.get_test_irmc_info()

View File

@ -21,6 +21,7 @@ from ironic.drivers import irmc
from ironic.drivers.modules import agent
from ironic.drivers.modules import inspector
from ironic.drivers.modules import ipmitool
from ironic.drivers.modules.irmc import bios as irmc_bios
from ironic.drivers.modules.irmc import raid
from ironic.drivers.modules import iscsi_deploy
from ironic.drivers.modules import noop
@ -42,7 +43,8 @@ class IRMCHardwareTestCase(db_base.DbTestCase):
enabled_management_interfaces=['irmc'],
enabled_power_interfaces=['irmc', 'ipmitool'],
enabled_raid_interfaces=['no-raid', 'agent', 'irmc'],
enabled_rescue_interfaces=['no-rescue', 'agent'])
enabled_rescue_interfaces=['no-rescue', 'agent'],
enabled_bios_interfaces=['irmc', 'no-bios', 'fake'])
def test_default_interfaces(self):
node = obj_utils.create_test_node(self.context, driver='irmc')
@ -63,6 +65,8 @@ class IRMCHardwareTestCase(db_base.DbTestCase):
noop.NoRAID)
self.assertIsInstance(task.driver.rescue,
noop.NoRescue)
self.assertIsInstance(task.driver.bios,
irmc_bios.IRMCBIOS)
def test_override_with_inspector(self):
self.config(enabled_inspect_interfaces=['inspector', 'irmc'])
@ -157,3 +161,27 @@ class IRMCHardwareTestCase(db_base.DbTestCase):
raid.IRMCRAID)
self.assertIsInstance(task.driver.rescue,
agent.AgentRescue)
def test_override_with_bios_configuration(self):
node = obj_utils.create_test_node(
self.context, driver='irmc',
deploy_interface='direct',
rescue_interface='agent',
bios_interface='irmc')
with task_manager.acquire(self.context, node.id) as task:
self.assertIsInstance(task.driver.boot,
irmc.boot.IRMCVirtualMediaBoot)
self.assertIsInstance(task.driver.console,
ipmitool.IPMISocatConsole)
self.assertIsInstance(task.driver.deploy,
agent.AgentDeploy)
self.assertIsInstance(task.driver.inspect,
irmc.inspect.IRMCInspect)
self.assertIsInstance(task.driver.management,
irmc.management.IRMCManagement)
self.assertIsInstance(task.driver.power,
irmc.power.IRMCPower)
self.assertIsInstance(task.driver.bios,
irmc_bios.IRMCBIOS)
self.assertIsInstance(task.driver.rescue,
agent.AgentRescue)

View File

@ -0,0 +1,6 @@
---
features:
- |
Adds new ``bios`` interface to ``irmc`` hardware type. And provide
out-of-band BIOS configuration solution for iRMC driver which makes
the functionality available via manual cleaning.

View File

@ -54,6 +54,7 @@ ironic.dhcp =
ironic.hardware.interfaces.bios =
fake = ironic.drivers.modules.fake:FakeBIOS
irmc = ironic.drivers.modules.irmc.bios:IRMCBIOS
no-bios = ironic.drivers.modules.noop:NoBIOS
ironic.hardware.interfaces.boot =