Merge "Fix Lenovo UEFI boot loop in prepare_instance flow"
This commit is contained in:
commit
f7e8c40eec
3 files changed
+84
-14
No files matched your search
@@ -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>`_.
|
||||
Reference in new issue
Block a user