Improve `redfish` set-boot-mode implementation

Refrain from touching unrelated boot params when setting boot mode
through Redfish BMC.

This should hopefully improve interoperability with Redfish BMCs.

NOTE: This is a backport with mofications to support the newer sushy
      library and allow older branches and thus users of ironic to
      have a path to navigate issues centered around the
      BootModeOverride with UEFI.

Change-Id: I461e54012045463b5e97daf26a93eb9dce6f6964
Story: 2007355
Task: 38910
(cherry picked from commit 5be4c4f5c5)
This commit is contained in:
Ilya Etingof 2020-03-02 18:48:37 +01:00 committed by Dmitry Tantsur
parent de8ed3ec99
commit 8bc07ba053
4 changed files with 101 additions and 33 deletions

View File

@ -115,9 +115,15 @@ class RedfishManagement(base.ManagementInterface):
system = redfish_utils.get_system(task.node)
try:
system.set_system_boot_source(
BOOT_DEVICE_MAP_REV[device],
enabled=BOOT_DEVICE_PERSISTENT_MAP_REV[persistent])
try:
system.set_system_boot_options(
BOOT_DEVICE_MAP_REV[device],
enabled=BOOT_DEVICE_PERSISTENT_MAP_REV[persistent])
except AttributeError:
system.set_system_boot_source(
BOOT_DEVICE_MAP_REV[device],
enabled=BOOT_DEVICE_PERSISTENT_MAP_REV[persistent])
except sushy.exceptions.SushyError as e:
error_msg = (_('Redfish set boot device failed for node '
'%(node)s. Error: %(error)s') %
@ -176,28 +182,30 @@ class RedfishManagement(base.ManagementInterface):
: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])
try:
system.set_system_boot_options(mode=BOOT_MODE_MAP_REV[mode])
except AttributeError:
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)
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 '

View File

@ -86,6 +86,31 @@ class RedfishManagementTestCase(db_base.DbTestCase):
(boot_devices.BIOS, sushy.BOOT_SOURCE_TARGET_BIOS_SETUP)
]
for target, expected in expected_values:
task.driver.management.set_boot_device(task, target)
# Asserts
fake_system.set_system_boot_options.assert_called_once_with(
expected, enabled=sushy.BOOT_SOURCE_ENABLED_ONCE)
mock_get_system.assert_called_once_with(task.node)
# Reset mocks
fake_system.set_system_boot_options.reset_mock()
mock_get_system.reset_mock()
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_set_boot_device_fallback(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_devices.PXE, sushy.BOOT_SOURCE_TARGET_PXE),
(boot_devices.DISK, sushy.BOOT_SOURCE_TARGET_HDD),
(boot_devices.CDROM, sushy.BOOT_SOURCE_TARGET_CD),
(boot_devices.BIOS, sushy.BOOT_SOURCE_TARGET_BIOS_SETUP)
]
fake_system.set_system_boot_options.side_effect = AttributeError
for target, expected in expected_values:
task.driver.management.set_boot_device(task, target)
@ -95,7 +120,7 @@ class RedfishManagementTestCase(db_base.DbTestCase):
mock_get_system.assert_called_once_with(task.node)
# Reset mocks
fake_system.set_system_boot_source.reset_mock()
fake_system.set_system_boot_options.reset_mock()
mock_get_system.reset_mock()
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
@ -113,19 +138,19 @@ class RedfishManagementTestCase(db_base.DbTestCase):
task.driver.management.set_boot_device(
task, boot_devices.PXE, persistent=target)
fake_system.set_system_boot_source.assert_called_once_with(
fake_system.set_system_boot_options.assert_called_once_with(
sushy.BOOT_SOURCE_TARGET_PXE, enabled=expected)
mock_get_system.assert_called_once_with(task.node)
# Reset mocks
fake_system.set_system_boot_source.reset_mock()
fake_system.set_system_boot_options.reset_mock()
mock_get_system.reset_mock()
@mock.patch.object(sushy, 'Sushy', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_set_boot_device_fail(self, mock_get_system, mock_sushy):
fake_system = mock.Mock()
fake_system.set_system_boot_source.side_effect = (
fake_system.set_system_boot_options.side_effect = (
sushy.exceptions.SushyError()
)
mock_get_system.return_value = fake_system
@ -134,7 +159,7 @@ class RedfishManagementTestCase(db_base.DbTestCase):
self.assertRaisesRegex(
exception.RedfishError, 'Redfish set boot device',
task.driver.management.set_boot_device, task, boot_devices.PXE)
fake_system.set_system_boot_source.assert_called_once_with(
fake_system.set_system_boot_options.assert_called_once_with(
sushy.BOOT_SOURCE_TARGET_PXE,
enabled=sushy.BOOT_SOURCE_ENABLED_ONCE)
mock_get_system.assert_called_once_with(task.node)
@ -173,6 +198,29 @@ class RedfishManagementTestCase(db_base.DbTestCase):
(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_options.assert_called_once_with(
mode=mode)
mock_get_system.assert_called_once_with(task.node)
# Reset mocks
fake_system.set_system_boot_options.reset_mock()
mock_get_system.reset_mock()
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_set_boot_mode_fallback(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)
]
fake_system.set_system_boot_options.side_effect = AttributeError
for mode, expected in expected_values:
task.driver.management.set_boot_mode(task, mode=mode)
@ -189,7 +237,7 @@ class RedfishManagementTestCase(db_base.DbTestCase):
@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()
fake_system.set_system_boot_source.side_effect = (
fake_system.set_system_boot_options.side_effect = (
sushy.exceptions.SushyError)
mock_get_system.return_value = fake_system
with task_manager.acquire(self.context, self.node.uuid,
@ -197,8 +245,8 @@ class RedfishManagementTestCase(db_base.DbTestCase):
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)
fake_system.set_system_boot_options.assert_called_once_with(
mode=boot_modes.UEFI)
mock_get_system.assert_called_once_with(task.node)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)

View File

@ -0,0 +1,12 @@
---
fixes:
- |
Improves interoperability with Redfish BMCs by untying node boot
mode change from other boot parameters change (such as boot device,
boot frequency). This fix requires a newer version of the
``sushy`` library, version ``3.2.0``.
upgrade:
- |
The version of ``sushy`` can now be updated to ``3.2.0`` or later to
address issues with managing persistant boot mode setting with Redfish
Baseboard Management Controllers.

View File

@ -42,8 +42,8 @@
IRONIC_TEMPEST_WHOLE_DISK_IMAGE: False
IRONIC_VM_COUNT: 1
IRONIC_VM_EPHEMERAL_DISK: 1
IRONIC_VM_LOG_DIR: '{{ devstack_base_dir }}/ironic-bm-logs'
IRONIC_VM_SPECS_RAM: 384
IRONIC_VM_LOG_DIR: '{{ devstack_base_dir }}/ironic-bm-logs'
# NOTE(dtantsur): in some jobs we end up with 12 disks total, so reduce
# each of them. For don't need all 10 GiB for CirrOS anyway.
IRONIC_VM_SPECS_DISK: 4