Merge "Option to enable bootloader config failure bypass"
This commit is contained in:
commit
b42719f3fa
@ -300,6 +300,18 @@ cli_opts = [
|
|||||||
'via lldp. If "all" is set then IPA should attempt '
|
'via lldp. If "all" is set then IPA should attempt '
|
||||||
'to bring up all VLANs from lldp on all interfaces. '
|
'to bring up all VLANs from lldp on all interfaces. '
|
||||||
'By default, no VLANs will be brought up.'),
|
'By default, no VLANs will be brought up.'),
|
||||||
|
cfg.BoolOpt('ignore_bootloader_failure',
|
||||||
|
default=APARAMS.get('ipa-ignore-bootloader-failure'),
|
||||||
|
help='If the agent should ignore failures to install a '
|
||||||
|
'bootloader configuration into UEFI NVRAM. This '
|
||||||
|
'option should only be considered if the hardware '
|
||||||
|
'is automatically searching and adding UEFI '
|
||||||
|
'bootloaders from partitions. Use on a system '
|
||||||
|
'which is NOT doing this will likely cause the '
|
||||||
|
'deployment to fail. This setting should only be '
|
||||||
|
'used if you are absolutely sure of what you are '
|
||||||
|
'doing and that your hardware supports '
|
||||||
|
'such functionality. Hint: Most hardware does not.'),
|
||||||
]
|
]
|
||||||
|
|
||||||
CONF.register_cli_opts(cli_opts)
|
CONF.register_cli_opts(cli_opts)
|
||||||
|
@ -22,6 +22,7 @@ import tempfile
|
|||||||
|
|
||||||
from ironic_lib import utils as ilib_utils
|
from ironic_lib import utils as ilib_utils
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from ironic_python_agent import errors
|
from ironic_python_agent import errors
|
||||||
@ -33,6 +34,7 @@ from ironic_python_agent import utils
|
|||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
BIND_MOUNTS = ('/dev', '/proc', '/run')
|
BIND_MOUNTS = ('/dev', '/proc', '/run')
|
||||||
|
|
||||||
@ -712,7 +714,8 @@ class ImageExtension(base.BaseAgentExtension):
|
|||||||
@base.async_command('install_bootloader')
|
@base.async_command('install_bootloader')
|
||||||
def install_bootloader(self, root_uuid, efi_system_part_uuid=None,
|
def install_bootloader(self, root_uuid, efi_system_part_uuid=None,
|
||||||
prep_boot_part_uuid=None,
|
prep_boot_part_uuid=None,
|
||||||
target_boot_mode='bios'):
|
target_boot_mode='bios',
|
||||||
|
ignore_bootloader_failure=None):
|
||||||
"""Install the GRUB2 bootloader on the image.
|
"""Install the GRUB2 bootloader on the image.
|
||||||
|
|
||||||
:param root_uuid: The UUID of the root partition.
|
:param root_uuid: The UUID of the root partition.
|
||||||
@ -734,6 +737,13 @@ class ImageExtension(base.BaseAgentExtension):
|
|||||||
if self.agent.iscsi_started:
|
if self.agent.iscsi_started:
|
||||||
iscsi.clean_up(device)
|
iscsi.clean_up(device)
|
||||||
|
|
||||||
|
# Always allow the API client to be the final word on if this is
|
||||||
|
# overridden or not.
|
||||||
|
if ignore_bootloader_failure is None:
|
||||||
|
ignore_failure = CONF.ignore_bootloader_failure
|
||||||
|
else:
|
||||||
|
ignore_failure = ignore_bootloader_failure
|
||||||
|
|
||||||
boot = hardware.dispatch_to_managers('get_boot_info')
|
boot = hardware.dispatch_to_managers('get_boot_info')
|
||||||
if boot.current_boot_mode != target_boot_mode:
|
if boot.current_boot_mode != target_boot_mode:
|
||||||
LOG.warning('Boot mode mismatch: target boot mode is %(target)s, '
|
LOG.warning('Boot mode mismatch: target boot mode is %(target)s, '
|
||||||
@ -753,9 +763,15 @@ class ImageExtension(base.BaseAgentExtension):
|
|||||||
has_efibootmgr = False
|
has_efibootmgr = False
|
||||||
|
|
||||||
if has_efibootmgr:
|
if has_efibootmgr:
|
||||||
if _manage_uefi(device,
|
try:
|
||||||
efi_system_part_uuid=efi_system_part_uuid):
|
if _manage_uefi(
|
||||||
return
|
device,
|
||||||
|
efi_system_part_uuid=efi_system_part_uuid):
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error('Error setting up bootloader. Error %s', e)
|
||||||
|
if not ignore_failure:
|
||||||
|
raise
|
||||||
|
|
||||||
# We don't have a working root UUID detection for whole disk images.
|
# We don't have a working root UUID detection for whole disk images.
|
||||||
# Until we can do it, avoid a confusing traceback.
|
# Until we can do it, avoid a confusing traceback.
|
||||||
@ -766,8 +782,13 @@ class ImageExtension(base.BaseAgentExtension):
|
|||||||
|
|
||||||
# In case we can't use efibootmgr for uefi we will continue using grub2
|
# In case we can't use efibootmgr for uefi we will continue using grub2
|
||||||
LOG.debug('Using grub2-install to set up boot files')
|
LOG.debug('Using grub2-install to set up boot files')
|
||||||
_install_grub2(device,
|
try:
|
||||||
root_uuid=root_uuid,
|
_install_grub2(device,
|
||||||
efi_system_part_uuid=efi_system_part_uuid,
|
root_uuid=root_uuid,
|
||||||
prep_boot_part_uuid=prep_boot_part_uuid,
|
efi_system_part_uuid=efi_system_part_uuid,
|
||||||
target_boot_mode=target_boot_mode)
|
prep_boot_part_uuid=prep_boot_part_uuid,
|
||||||
|
target_boot_mode=target_boot_mode)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error('Error setting up bootloader. Error %s', e)
|
||||||
|
if not ignore_failure:
|
||||||
|
raise
|
||||||
|
@ -95,6 +95,127 @@ class TestImageExtension(base.IronicAgentTest):
|
|||||||
)
|
)
|
||||||
mock_iscsi_clean.assert_called_once_with(self.fake_dev)
|
mock_iscsi_clean.assert_called_once_with(self.fake_dev)
|
||||||
|
|
||||||
|
@mock.patch.object(iscsi, 'clean_up', autospec=True)
|
||||||
|
@mock.patch.object(image, '_manage_uefi', autospec=True)
|
||||||
|
@mock.patch.object(image, '_install_grub2', autospec=True)
|
||||||
|
def test__install_bootloader_uefi_ignores_manage_failure(
|
||||||
|
self, mock_grub2, mock_uefi,
|
||||||
|
mock_iscsi_clean,
|
||||||
|
mock_execute, mock_dispatch):
|
||||||
|
self.config(ignore_bootloader_failure=True)
|
||||||
|
mock_uefi.side_effect = OSError('meow')
|
||||||
|
mock_dispatch.side_effect = [
|
||||||
|
self.fake_dev, hardware.BootInfo(current_boot_mode='uefi')
|
||||||
|
]
|
||||||
|
mock_uefi.return_value = False
|
||||||
|
self.agent_extension.install_bootloader(
|
||||||
|
root_uuid=self.fake_root_uuid,
|
||||||
|
efi_system_part_uuid=self.fake_efi_system_part_uuid,
|
||||||
|
target_boot_mode='uefi'
|
||||||
|
).join()
|
||||||
|
mock_dispatch.assert_any_call('get_os_install_device')
|
||||||
|
mock_dispatch.assert_any_call('get_boot_info')
|
||||||
|
self.assertEqual(2, mock_dispatch.call_count)
|
||||||
|
mock_grub2.assert_called_once_with(
|
||||||
|
self.fake_dev,
|
||||||
|
root_uuid=self.fake_root_uuid,
|
||||||
|
efi_system_part_uuid=self.fake_efi_system_part_uuid,
|
||||||
|
prep_boot_part_uuid=None,
|
||||||
|
target_boot_mode='uefi'
|
||||||
|
)
|
||||||
|
mock_iscsi_clean.assert_called_once_with(self.fake_dev)
|
||||||
|
|
||||||
|
@mock.patch.object(iscsi, 'clean_up', autospec=True)
|
||||||
|
@mock.patch.object(image, '_manage_uefi', autospec=True)
|
||||||
|
@mock.patch.object(image, '_install_grub2', autospec=True)
|
||||||
|
def test__install_bootloader_uefi_ignores_grub_failure(
|
||||||
|
self, mock_grub2, mock_uefi,
|
||||||
|
mock_iscsi_clean,
|
||||||
|
mock_execute, mock_dispatch):
|
||||||
|
self.config(ignore_bootloader_failure=True)
|
||||||
|
mock_grub2.side_effect = OSError('meow')
|
||||||
|
mock_dispatch.side_effect = [
|
||||||
|
self.fake_dev, hardware.BootInfo(current_boot_mode='uefi')
|
||||||
|
]
|
||||||
|
mock_uefi.return_value = False
|
||||||
|
self.agent_extension.install_bootloader(
|
||||||
|
root_uuid=self.fake_root_uuid,
|
||||||
|
efi_system_part_uuid=self.fake_efi_system_part_uuid,
|
||||||
|
target_boot_mode='uefi'
|
||||||
|
).join()
|
||||||
|
mock_dispatch.assert_any_call('get_os_install_device')
|
||||||
|
mock_dispatch.assert_any_call('get_boot_info')
|
||||||
|
self.assertEqual(2, mock_dispatch.call_count)
|
||||||
|
mock_grub2.assert_called_once_with(
|
||||||
|
self.fake_dev,
|
||||||
|
root_uuid=self.fake_root_uuid,
|
||||||
|
efi_system_part_uuid=self.fake_efi_system_part_uuid,
|
||||||
|
prep_boot_part_uuid=None,
|
||||||
|
target_boot_mode='uefi'
|
||||||
|
)
|
||||||
|
mock_iscsi_clean.assert_called_once_with(self.fake_dev)
|
||||||
|
|
||||||
|
@mock.patch.object(iscsi, 'clean_up', autospec=True)
|
||||||
|
@mock.patch.object(image, '_manage_uefi', autospec=True)
|
||||||
|
@mock.patch.object(image, '_install_grub2', autospec=True)
|
||||||
|
def test__install_bootloader_uefi_ignores_grub_failure_api_override(
|
||||||
|
self, mock_grub2, mock_uefi,
|
||||||
|
mock_iscsi_clean,
|
||||||
|
mock_execute, mock_dispatch):
|
||||||
|
self.config(ignore_bootloader_failure=False)
|
||||||
|
mock_grub2.side_effect = OSError('meow')
|
||||||
|
mock_dispatch.side_effect = [
|
||||||
|
self.fake_dev, hardware.BootInfo(current_boot_mode='uefi')
|
||||||
|
]
|
||||||
|
mock_uefi.return_value = False
|
||||||
|
self.agent_extension.install_bootloader(
|
||||||
|
root_uuid=self.fake_root_uuid,
|
||||||
|
efi_system_part_uuid=self.fake_efi_system_part_uuid,
|
||||||
|
target_boot_mode='uefi', ignore_bootloader_failure=True,
|
||||||
|
).join()
|
||||||
|
mock_dispatch.assert_any_call('get_os_install_device')
|
||||||
|
mock_dispatch.assert_any_call('get_boot_info')
|
||||||
|
self.assertEqual(2, mock_dispatch.call_count)
|
||||||
|
mock_grub2.assert_called_once_with(
|
||||||
|
self.fake_dev,
|
||||||
|
root_uuid=self.fake_root_uuid,
|
||||||
|
efi_system_part_uuid=self.fake_efi_system_part_uuid,
|
||||||
|
prep_boot_part_uuid=None,
|
||||||
|
target_boot_mode='uefi'
|
||||||
|
)
|
||||||
|
mock_iscsi_clean.assert_called_once_with(self.fake_dev)
|
||||||
|
|
||||||
|
@mock.patch.object(iscsi, 'clean_up', autospec=True)
|
||||||
|
@mock.patch.object(image, '_manage_uefi', autospec=True)
|
||||||
|
@mock.patch.object(image, '_install_grub2', autospec=True)
|
||||||
|
def test__install_bootloader_uefi_grub_failure_api_override(
|
||||||
|
self, mock_grub2, mock_uefi,
|
||||||
|
mock_iscsi_clean,
|
||||||
|
mock_execute, mock_dispatch):
|
||||||
|
self.config(ignore_bootloader_failure=True)
|
||||||
|
mock_grub2.side_effect = OSError('meow')
|
||||||
|
mock_dispatch.side_effect = [
|
||||||
|
self.fake_dev, hardware.BootInfo(current_boot_mode='uefi')
|
||||||
|
]
|
||||||
|
mock_uefi.return_value = False
|
||||||
|
result = self.agent_extension.install_bootloader(
|
||||||
|
root_uuid=self.fake_root_uuid,
|
||||||
|
efi_system_part_uuid=self.fake_efi_system_part_uuid,
|
||||||
|
target_boot_mode='uefi', ignore_bootloader_failure=False,
|
||||||
|
).join()
|
||||||
|
self.assertIsNotNone(result.command_error)
|
||||||
|
mock_dispatch.assert_any_call('get_os_install_device')
|
||||||
|
mock_dispatch.assert_any_call('get_boot_info')
|
||||||
|
self.assertEqual(2, mock_dispatch.call_count)
|
||||||
|
mock_grub2.assert_called_once_with(
|
||||||
|
self.fake_dev,
|
||||||
|
root_uuid=self.fake_root_uuid,
|
||||||
|
efi_system_part_uuid=self.fake_efi_system_part_uuid,
|
||||||
|
prep_boot_part_uuid=None,
|
||||||
|
target_boot_mode='uefi'
|
||||||
|
)
|
||||||
|
mock_iscsi_clean.assert_called_once_with(self.fake_dev)
|
||||||
|
|
||||||
@mock.patch.object(iscsi, 'clean_up', autospec=True)
|
@mock.patch.object(iscsi, 'clean_up', autospec=True)
|
||||||
@mock.patch.object(image, '_install_grub2', autospec=True)
|
@mock.patch.object(image, '_install_grub2', autospec=True)
|
||||||
def test__install_bootloader_no_root(self, mock_grub2, mock_iscsi_clean,
|
def test__install_bootloader_no_root(self, mock_grub2, mock_iscsi_clean,
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Adds an configuration option which can be encoded into the ramdisk itself
|
||||||
|
or the PXE parameters being provided to instruct the agent to ignore
|
||||||
|
bootloader installation or configuration failures. This functionality is
|
||||||
|
useful to work around well-intentioned hardware which is auto-populating
|
||||||
|
all possible device into the UEFI nvram firmware in order to try and help
|
||||||
|
ensure the machine boots. Except, this can also mean any
|
||||||
|
explict configuration attempt will fail. Operators needing this bypass
|
||||||
|
can use the ``ipa-ignore-bootloader-failure`` configuration option on the
|
||||||
|
PXE command line or utilize the ``ignore_bootloader_failure`` option
|
||||||
|
for the Ramdisk configuration.
|
||||||
|
In a future version of ironic, this setting may be able to be overriden
|
||||||
|
by ironic node level configuration.
|
Loading…
Reference in New Issue
Block a user