Merge "Implement iRMC BIOS configuration"
This commit is contained in:
commit
f70770db02
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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."""
|
||||
|
140
ironic/drivers/modules/irmc/bios.py
Normal file
140
ironic/drivers/modules/irmc/bios.py
Normal 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)
|
152
ironic/tests/unit/drivers/modules/irmc/test_bios.py
Normal file
152
ironic/tests/unit/drivers/modules/irmc/test_bios.py
Normal 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)
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user