Added Redfish boot mode management

This change adds node boot mode management to
the Redfish driver.

Story: 1731013
Task: 9271
Change-Id: I3aa11cc0ce9da9cf6c801300370bd7ce420f434a
This commit is contained in:
Ilya Etingof 2018-02-05 13:09:29 +01:00
parent ce9bdbffb1
commit 7c5f655794
5 changed files with 156 additions and 4 deletions

View File

@ -17,6 +17,7 @@ from oslo_log import log
from oslo_utils import importutils
from ironic.common import boot_devices
from ironic.common import boot_modes
from ironic.common import exception
from ironic.common.i18n import _
from ironic.conductor import task_manager
@ -37,6 +38,13 @@ if sushy:
BOOT_DEVICE_MAP_REV = {v: k for k, v in BOOT_DEVICE_MAP.items()}
BOOT_MODE_MAP = {
sushy.BOOT_SOURCE_MODE_UEFI: boot_modes.UEFI,
sushy.BOOT_SOURCE_MODE_BIOS: boot_modes.LEGACY_BIOS
}
BOOT_MODE_MAP_REV = {v: k for k, v in BOOT_MODE_MAP.items()}
BOOT_DEVICE_PERSISTENT_MAP = {
sushy.BOOT_SOURCE_ENABLED_CONTINUOUS: True,
sushy.BOOT_SOURCE_ENABLED_ONCE: False
@ -103,9 +111,7 @@ class RedfishManagement(base.ManagementInterface):
:raises: RedfishError on an error from the Sushy library
"""
system = redfish_utils.get_system(task.node)
# TODO(lucasagomes): set_system_boot_source() also supports mode
# for UEFI and BIOS we should get it from instance_info and pass
# it along this call
try:
system.set_system_boot_source(
BOOT_DEVICE_MAP_REV[device],
@ -141,6 +147,81 @@ class RedfishManagement(base.ManagementInterface):
'persistent': BOOT_DEVICE_PERSISTENT_MAP.get(
system.boot.get('enabled'))}
def get_supported_boot_modes(self, task):
"""Get a list of the supported boot modes.
:param task: A task from TaskManager.
:returns: A list with the supported boot modes defined
in :mod:`ironic.common.boot_modes`. If boot
mode support can't be determined, empty list
is returned.
"""
return list(BOOT_MODE_MAP_REV)
@task_manager.require_exclusive_lock
def set_boot_mode(self, task, mode):
"""Set the boot mode for a node.
Set the boot mode to use on next reboot of the node.
:param task: A task from TaskManager.
:param mode: The boot mode, one of
:mod:`ironic.common.boot_modes`.
:raises: InvalidParameterValue if an invalid boot mode is
specified.
:raises: MissingParameterValue if a required parameter is missing
:raises: RedfishConnectionError when it fails to connect to Redfish
:raises: RedfishError on an error from the Sushy library
"""
system = redfish_utils.get_system(task.node)
boot_device = system.boot.get('target')
if not boot_device:
error_msg = (_('Cannot change boot mode on node %(node)s '
'because its boot device is not set.') %
{'node': task.node.uuid})
LOG.error(error_msg)
raise exception.RedfishError(error_msg)
boot_override = system.boot.get('enabled')
if not boot_override:
error_msg = (_('Cannot change boot mode on node %(node)s '
'because its boot source override is not set.') %
{'node': task.node.uuid})
LOG.error(error_msg)
raise exception.RedfishError(error_msg)
try:
system.set_system_boot_source(
boot_device,
enabled=boot_override,
mode=BOOT_MODE_MAP_REV[mode])
except sushy.exceptions.SushyError as e:
error_msg = (_('Setting boot mode to %(mode)s '
'failed for node %(node)s. '
'Error: %(error)s') %
{'node': task.node.uuid, 'mode': mode,
'error': e})
LOG.error(error_msg)
raise exception.RedfishError(error=error_msg)
def get_boot_mode(self, task):
"""Get the current boot mode for a node.
Provides the current boot mode of the node.
:param task: A task from TaskManager.
:raises: MissingParameterValue if a required parameter is missing
:raises: DriverOperationError or its derivative in case
of driver runtime error.
:returns: The boot mode, one of :mod:`ironic.common.boot_mode` or
None if it is unknown.
"""
system = redfish_utils.get_system(task.node)
return BOOT_MODE_MAP.get(system.boot.get('mode'))
def get_sensors_data(self, task):
"""Get sensors data.

View File

@ -17,6 +17,7 @@ import mock
from oslo_utils import importutils
from ironic.common import boot_devices
from ironic.common import boot_modes
from ironic.common import exception
from ironic.conductor import task_manager
from ironic.drivers.modules.redfish import management as redfish_mgmt
@ -153,6 +154,68 @@ class RedfishManagementTestCase(db_base.DbTestCase):
'persistent': True}
self.assertEqual(expected, response)
def test_get_supported_boot_modes(self):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
supported_boot_modes = (
task.driver.management.get_supported_boot_modes(task))
self.assertEqual(list(redfish_mgmt.BOOT_MODE_MAP_REV),
supported_boot_modes)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_set_boot_mode(self, mock_get_system):
fake_system = mock.Mock()
mock_get_system.return_value = fake_system
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
expected_values = [
(boot_modes.LEGACY_BIOS, sushy.BOOT_SOURCE_MODE_BIOS),
(boot_modes.UEFI, sushy.BOOT_SOURCE_MODE_UEFI)
]
for mode, expected in expected_values:
task.driver.management.set_boot_mode(task, mode=mode)
# Asserts
fake_system.set_system_boot_source.assert_called_once_with(
mock.ANY, enabled=mock.ANY, mode=mode)
mock_get_system.assert_called_once_with(task.node)
# Reset mocks
fake_system.set_system_boot_source.reset_mock()
mock_get_system.reset_mock()
@mock.patch('ironic.drivers.modules.redfish.management.sushy')
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_set_boot_mode_fail(self, mock_get_system, mock_sushy):
fake_system = mock.Mock()
mock_sushy.exceptions.SushyError = MockedSushyError
fake_system.set_system_boot_source.side_effect = MockedSushyError
mock_get_system.return_value = fake_system
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
self.assertRaisesRegex(
exception.RedfishError, 'Setting boot mode',
task.driver.management.set_boot_mode, task, boot_modes.UEFI)
fake_system.set_system_boot_source.assert_called_once_with(
mock.ANY, enabled=mock.ANY, mode=boot_modes.UEFI)
mock_get_system.assert_called_once_with(task.node)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_get_boot_mode(self, mock_get_system):
boot_attribute = {
'target': sushy.BOOT_SOURCE_TARGET_PXE,
'enabled': sushy.BOOT_SOURCE_ENABLED_CONTINUOUS,
'mode': sushy.BOOT_SOURCE_MODE_BIOS,
}
fake_system = mock.Mock(boot=boot_attribute)
mock_get_system.return_value = fake_system
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
response = task.driver.management.get_boot_mode(task)
expected = boot_modes.LEGACY_BIOS
self.assertEqual(expected, response)
def test_get_sensors_data(self):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:

View File

@ -163,6 +163,8 @@ SUSHY_CONSTANTS_SPEC = (
'RESET_NMI',
'BOOT_SOURCE_ENABLED_CONTINUOUS',
'BOOT_SOURCE_ENABLED_ONCE',
'BOOT_SOURCE_MODE_BIOS',
'BOOT_SOURCE_MODE_UEFI',
)
XCLARITY_SPEC = (

View File

@ -241,7 +241,9 @@ if not sushy:
RESET_FORCE_RESTART='force restart',
RESET_NMI='nmi',
BOOT_SOURCE_ENABLED_CONTINUOUS='continuous',
BOOT_SOURCE_ENABLED_ONCE='once'
BOOT_SOURCE_ENABLED_ONCE='once',
BOOT_SOURCE_MODE_BIOS='bios',
BOOT_SOURCE_MODE_UEFI='uefi'
)
sys.modules['sushy'] = sushy

View File

@ -0,0 +1,4 @@
---
features:
- Adds support for reading and setting bare metal node's boot mode
using the ``redfish`` management interface.