IPMITool: Check the boot mode when setting the boot device

This patch is fixing a problem where the ipmitool option "efiboot"
wasn't passed as part of the command to set the boot device for UEFI
nodes causing them to switch back to legacy BIOS.

A FIXME note was left in the code. It seems that ipmitool has a problem
setting the efiboot + persistent options at the same time
(see bug #1611306). It seems multiple options ought to be specified
together, and that works, except for efiboot. Therefore this patch is
using raw bytes to change the boot device when persistent and uefi are
specified, that way it will work for newer and older versions of the
ipmitool utility.

Closes-Bug: #1611306
Change-Id: I11456fb035fe41fe46f1ff04e4d56c9f85b9e19d
This commit is contained in:
Lucas Alvares Gomes 2016-08-10 10:06:22 +01:00
parent bb4933922e
commit 6419cc2118
3 changed files with 77 additions and 4 deletions

View File

@ -57,6 +57,7 @@ from ironic.common import utils
from ironic.conductor import task_manager
from ironic.drivers import base
from ironic.drivers.modules import console_utils
from ironic.drivers.modules import deploy_utils
from ironic.drivers import utils as driver_utils
@ -138,6 +139,18 @@ ipmitool_command_options = {
# form regardless of locale.
IPMITOOL_RETRYABLE_FAILURES = ['insufficient resources for session']
# NOTE(lucasagomes): A mapping for the boot devices and their hexadecimal
# value. For more information about these values see the "Set System Boot
# Options Command" section of the link below (page 418)
# http://www.intel.com/content/www/us/en/servers/ipmi/ipmi-second-gen-interface-spec-v2-rev1-1.html # noqa
BOOT_DEVICE_HEXA_MAP = {
boot_devices.PXE: '0x04',
boot_devices.DISK: '0x08',
boot_devices.CDROM: '0x14',
boot_devices.BIOS: '0x18',
boot_devices.SAFE: '0x0c'
}
def _check_option_support(options):
"""Checks if the specific ipmitool options are supported on host.
@ -871,8 +884,7 @@ class IPMIManagement(base.ManagementInterface):
in :mod:`ironic.common.boot_devices`.
"""
return [boot_devices.PXE, boot_devices.DISK, boot_devices.CDROM,
boot_devices.BIOS, boot_devices.SAFE]
return list(BOOT_DEVICE_HEXA_MAP)
@METRICS.timer('IPMIManagement.set_boot_device')
@task_manager.require_exclusive_lock
@ -912,9 +924,32 @@ class IPMIManagement(base.ManagementInterface):
# persistent or we do not have admin rights.
persistent = False
cmd = "chassis bootdev %s" % device
# FIXME(lucasagomes): Older versions of the ipmitool utility
# are not able to set the options "efiboot" and "persistent"
# at the same time, combining other options seems to work fine,
# except efiboot. Newer versions of ipmitool (1.8.17) does fix
# this problem but (some) distros still packaging an older version.
# To workaround this problem for now we can make use of sending
# raw bytes to set the boot device for a node in persistent +
# uefi mode, this will work with newer and older versions of the
# ipmitool utility. Also see:
# https://bugs.launchpad.net/ironic/+bug/1611306
boot_mode = deploy_utils.get_boot_mode_for_deploy(task.node)
if persistent and boot_mode == 'uefi':
raw_cmd = ('0x00 0x08 0x05 0xe0 %s 0x00 0x00 0x00' %
BOOT_DEVICE_HEXA_MAP[device])
send_raw(task, raw_cmd)
return
options = []
if persistent:
cmd = cmd + " options=persistent"
options.append('persistent')
if boot_mode == 'uefi':
options.append('efiboot')
cmd = "chassis bootdev %s" % device
if options:
cmd = cmd + " options=%s" % ','.join(options)
driver_info = _parse_driver_info(task.node)
try:
out, err = _exec_ipmitool(driver_info, cmd)

View File

@ -42,6 +42,7 @@ from ironic.common import states
from ironic.common import utils
from ironic.conductor import task_manager
from ironic.drivers.modules import console_utils
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules import ipmitool as ipmi
from ironic.drivers import utils as driver_utils
from ironic.tests import base
@ -1789,6 +1790,38 @@ class IPMIToolDriverTestCase(db_base.DbTestCase):
self.driver.management.set_boot_device,
task, boot_devices.PXE)
@mock.patch.object(deploy_utils, 'get_boot_mode_for_deploy')
@mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
def test_management_interface_set_boot_device_uefi(self, mock_exec,
mock_boot_mode):
mock_boot_mode.return_value = 'uefi'
mock_exec.return_value = [None, None]
with task_manager.acquire(self.context, self.node.uuid) as task:
self.driver.management.set_boot_device(task, boot_devices.PXE)
mock_calls = [
mock.call(self.info, "raw 0x00 0x08 0x03 0x08"),
mock.call(self.info, "chassis bootdev pxe options=efiboot")
]
mock_exec.assert_has_calls(mock_calls)
@mock.patch.object(deploy_utils, 'get_boot_mode_for_deploy')
@mock.patch.object(ipmi, '_exec_ipmitool', autospec=True)
def test_management_interface_set_boot_device_uefi_and_persistent(
self, mock_exec, mock_boot_mode):
mock_boot_mode.return_value = 'uefi'
mock_exec.return_value = [None, None]
with task_manager.acquire(self.context, self.node.uuid) as task:
self.driver.management.set_boot_device(task, boot_devices.PXE,
persistent=True)
mock_calls = [
mock.call(self.info, "raw 0x00 0x08 0x03 0x08"),
mock.call(self.info, "raw 0x00 0x08 0x05 0xe0 0x04 0x00 0x00 0x00")
]
mock_exec.assert_has_calls(mock_calls)
def test_management_interface_get_supported_boot_devices(self):
with task_manager.acquire(self.context, self.node.uuid) as task:
expected = [boot_devices.PXE, boot_devices.DISK,

View File

@ -0,0 +1,5 @@
---
fixes:
- Fixes a problem where the boot mode (UEFI or BIOS) wasn't checked
as part of changing the boot device of a node, making it incorrectly
switch from UEFI to Legacy BIOS mode on some hardware models.