Merge "Fix Lenovo UEFI boot loop in prepare_instance flow"

This commit is contained in:
Zuul authored and Gerrit Code Review committed 2026-09-29 02:13:07 +00:00
commit f7e8c40eec
3 files changed
+84 -14

No files matched your search

+50 -12
View File
@@ -1157,9 +1157,9 @@ class RedfishVirtualMediaBoot(base.BootInterface):
boot_mode_utils.sync_boot_mode(task)
self._set_boot_device(task, boot_devices.CDROM)
self._apply_boot_device(task, boot_devices.CDROM)
LOG.debug("Node %(node)s is set to one time boot from "
LOG.debug("Node %(node)s is set to boot from "
"%(device)s", {'node': task.node.uuid,
'device': boot_devices.CDROM})
@@ -1212,11 +1212,12 @@ class RedfishVirtualMediaBoot(base.BootInterface):
boot_option = deploy_utils.get_boot_option(node)
iwdi = node.driver_internal_info.get('is_whole_disk_image')
if boot_option == "local" or iwdi:
self._set_boot_device(task, boot_devices.DISK, persistent=True)
self._apply_boot_device(
task, boot_devices.DISK, persistent=True)
LOG.debug("Node %(node)s is set to permanently boot from local "
"%(device)s", {'node': task.node.uuid,
'device': boot_devices.DISK})
LOG.debug("Node %(node)s set to boot from local %(device)s",
{'node': task.node.uuid,
'device': boot_devices.DISK})
return
params = {}
@@ -1228,7 +1229,8 @@ class RedfishVirtualMediaBoot(base.BootInterface):
"The UUID of the root partition could not be found for "
"node %s. Booting instance from disk anyway.", node.uuid)
self._set_boot_device(task, boot_devices.DISK, persistent=True)
self._apply_boot_device(
task, boot_devices.DISK, persistent=True)
return
@@ -1330,6 +1332,40 @@ class RedfishVirtualMediaBoot(base.BootInterface):
self._eject_all(task)
boot_mode_utils.deconfigure_secure_boot_if_needed(task)
@classmethod
def _apply_boot_device(cls, task, device, persistent=False):
"""Set boot device with vendor-specific handling.
Lenovo UEFI systems use an NVRAM-based boot model where
setting BootSourceOverrideTarget=Hdd maps to a generic
"Hard Disk" entry that bypasses the Red Hat shim bootloader,
causing a "Boot Option Restoration" loop. Skip the DISK
override entirely and let the UEFI boot order (which already
contains the shim entry) take effect.
See https://bugs.launchpad.net/ironic/+bug/2053064
:param task: a TaskManager instance.
:param device: the boot device, one of
:mod:`ironic.common.boot_devices`.
:param persistent: Whether to set next-boot, or make the change
permanent. Default: False.
:raises: InvalidParameterValue if the validation of the
ManagementInterface fails.
"""
vendor = task.node.properties.get('vendor')
target_boot_mode = boot_mode_utils.get_boot_mode(task.node)
if (vendor and vendor.lower() == 'lenovo'
and target_boot_mode == 'uefi'
and device == boot_devices.DISK and persistent):
LOG.debug('Skipping persistent boot device setting for '
'Lenovo UEFI node %s to allow UEFI boot order '
'(with shim bootloader) to take effect',
task.node.uuid)
return
cls._set_boot_device(task, device, persistent)
@classmethod
def _set_boot_device(cls, task, device, persistent=False):
"""Set the boot device for a node.
@@ -1573,11 +1609,12 @@ class RedfishHttpsBoot(base.BootInterface):
boot_option = deploy_utils.get_boot_option(node)
iwdi = node.driver_internal_info.get('is_whole_disk_image')
if boot_option == "local" or iwdi:
self._set_boot_device(task, boot_devices.DISK, persistent=True)
RedfishVirtualMediaBoot._apply_boot_device(
task, boot_devices.DISK, persistent=True)
LOG.debug("Node %(node)s is set to permanently boot from local "
"%(device)s", {'node': task.node.uuid,
'device': boot_devices.DISK})
LOG.debug("Node %(node)s set to boot from local %(device)s",
{'node': task.node.uuid,
'device': boot_devices.DISK})
return
params = {}
@@ -1589,7 +1626,8 @@ class RedfishHttpsBoot(base.BootInterface):
"The UUID of the root partition could not be found for "
"node %s. Booting instance from disk anyway.", node.uuid)
self._set_boot_device(task, boot_devices.DISK, persistent=True)
RedfishVirtualMediaBoot._apply_boot_device(
task, boot_devices.DISK, persistent=True)
return
@@ -1403,6 +1403,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task)
@mock.patch.object(boot_mode_utils, 'get_boot_mode', autospec=True)
@mock.patch.object(boot_mode_utils, 'configure_secure_boot_if_needed',
autospec=True)
@mock.patch.object(boot_mode_utils, 'sync_boot_mode', autospec=True)
@@ -1413,7 +1414,8 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
def _test_prepare_instance_local_boot(
self, mock_system, mock_manager_utils,
mock_cleanup_iso_image, mock__eject_vmedia, mock_sync_boot_mode,
mock_secure_boot):
mock_secure_boot, mock_get_boot_mode):
mock_get_boot_mode.return_value = 'bios'
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
@@ -3310,6 +3312,7 @@ class RedfishHTTPBootTestCase(db_base.DbTestCase):
mock_image_cleanup.assert_called_once_with(task)
mock_disk_cleanup.assert_not_called()
@mock.patch.object(boot_mode_utils, 'get_boot_mode', autospec=True)
@mock.patch.object(boot_mode_utils, 'configure_secure_boot_if_needed',
autospec=True)
@mock.patch.object(boot_mode_utils, 'sync_boot_mode', autospec=True)
@@ -3319,7 +3322,8 @@ class RedfishHTTPBootTestCase(db_base.DbTestCase):
def _test_prepare_instance_local_boot(
self, mock_system, mock_manager_utils,
mock_cleanup_iso_image, mock_sync_boot_mode,
mock_secure_boot):
mock_secure_boot, mock_get_boot_mode):
mock_get_boot_mode.return_value = 'bios'
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
@@ -0,0 +1,28 @@
---
fixes:
- |
Fixes an issue where Lenovo servers in UEFI mode would get stuck in an
infinite boot loop after Ironic provisioned RHCOS or other operating
systems that require UEFI shim bootloaders when using custom deploy
flows (e.g., OpenShift/Metal³ deployments with Redfish boot interfaces).
After provisioning, when preparing the instance to boot from disk, the
Redfish boot interfaces (``redfish-virtual-media`` and ``redfish-https``)
set ``BootSourceOverrideTarget=Hdd`` which on Lenovo systems maps to a
generic "Hard Disk" boot entry that bypasses the UEFI shim bootloader.
The system would display a "Boot Option Restoration" screen and
continuously reboot, requiring manual user intervention.
The fix extends existing Lenovo UEFI handling from the agent deploy
interface to the Redfish boot interfaces. During the ``prepare_instance``
phase, Ironic now skips setting the boot device for Lenovo UEFI systems,
allowing the UEFI boot order (which has the shim bootloader first) to
handle the boot process. This avoids triggering NVRAM changes that can
be lost due to Lenovo's NVRAM restore behavior.
This change only affects Lenovo hardware in UEFI mode during instance
preparation in the Redfish boot interfaces and does not affect legacy
BIOS mode, other vendors, or other boot interfaces.
For more details, see
`bug 2150628 <https://bugs.launchpad.net/ironic/+bug/2150628>`_.