Fix iDRAC hardware type does not work with UEFI

This fixes an issue that caused the integrated Dell Remote Access
Controller (iDRAC) management hardware interface implementation,
'idrac', to fail to boot nodes in Unified Extensible Firmware Interface
(UEFI) boot mode. That interface is supported by the 'idrac' hardware
type. The following errors appear in the ironic conductor log:

ERROR ironic.drivers.modules.agent_base_vendor   File
"/opt/stack/new/ironic/ironic/drivers/modules/drac/management.py", line
106, in set_boot_device
ERROR ironic.drivers.modules.agent_base_vendor     in
drac_boot_devices[PERSISTENT_BOOT_MODE]
ERROR ironic.drivers.modules.agent_base_vendor KeyError: 'IPL'
ERROR ironic.drivers.modules.agent_base_vendor
ERROR ironic.conductor.utils [None
req-80b5a61c-4ae1-41ea-875f-5a722142be01 None None] Error rebooting node
cddff129-7353-4de1-b436-810b1ae0dd92 after deploy. KeyError: 'IPL'
DEBUG ironic.common.states [None
req-80b5a61c-4ae1-41ea-875f-5a722142be01 None None] Exiting old state
'deploying' in response to event 'fail' {{(pid=23850) on_exit
/opt/stack/new/ironic/ironic/common/states.py:263}}
DEBUG ironic.common.states [None
req-80b5a61c-4ae1-41ea-875f-5a722142be01 None None] Entering new state
'deploy failed' in response to event 'fail' {{(pid=23850) on_enter
/opt/stack/new/ironic/ironic/common/states.py:269}}

The issue is resolved for Dell EMC PowerEdge 13th and 14th generation
servers. It is not resolved for PowerEdge 12th generation and earlier
servers.

Prior to this change, the set_boot_device() function in
ironic.drivers.modules.drac.management could successfully set a boot
device that would persist during future boots only when the server's
boot mode was configured to be BIOS. The root cause was that function
understood only a single persistent boot mode, BIOS, and it was hard
coded.

PERSISTENT_BOOT_MODE = 'IPL'

Initial Program Load (IPL) is a synonym for legacy BIOS boot mode.

The iDRAC does not report nor make visible the boot source lists for
persistent boot modes that are not configured as the server's current
boot mode. Only the list associated with the configured boot mode is
reported and can be modified. So, when the boot mode is configured to be
UEFI, the iDRAC permits only its boot source list to be modified.
However, set_boot_device() had been capable of only modifying the boot
source list for BIOS (IPL), which was not reported. Its attempt to
access the wrong, unreported boot source list failed.

This change fixes the issue by determining the configured persistent
boot mode at run-time and modifying its boot source list. The hard
coding of a persistent boot mode has been eliminated. Now, nodes managed
by the iDRAC hardware type successfully boot in either persistent boot
mode, BIOS or UEFI. More information about using the iDRAC for boot
management is available in the "Dell EMC BIOS and Boot Management
Profile", Version 4.0.0, section 7.2 Boot Management, pp. 44-47 [1].

The way the iDRAC hardware type deploys the operating system, by copying
it to a disk, posed an additional challenge to this fix. UEFI discovers
new boot sources during the next boot. How could the server be
configured to directly boot into the newly deployed operating system
when its boot source is unknown? It was overcome by using new iDRAC BIOS
attributes named 'SetBootOrderFqddNN' [2]. Those make it possible to
specify the boot source for the next boot without requiring it to be
present in the current boot's boot source list. The server is configured
to directly boot from the disk on which the operating system was just
deployed. An additional reboot is not required. Note that those new
attributes are presently available on only 13th and 14th generation
servers. Also, this approach is only possible for configuring a
persistent boot source.

[1] http://en.community.dell.com/techcenter/extras/m/white_papers/20444495
[2] "Configuring server boot options on 14th generation Dell EMC
PowerEdge servers", section 3.5.2 SetBootOrderFqddNN, pp. 13-14
(http://en.community.dell.com/techcenter/extras/m/white_papers/20487489)

Conflicts:
	ironic/tests/unit/drivers/modules/drac/test_management.py

Story: 1656841
Task: 9711
Change-Id: Idad1db1807420eab62c44318afafe10ea8c52ee5
(cherry picked from commit eb14f5750f)
This commit is contained in:
Richard Pioso 2017-12-25 16:29:58 -05:00
parent 27fea91b0b
commit a00a0bf357
3 changed files with 452 additions and 35 deletions

View File

@ -2,6 +2,7 @@
#
# Copyright 2014 Red Hat, Inc.
# All Rights Reserved.
# Copyright (c) 2017-2018 Dell Inc. or its subsidiaries.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -37,15 +38,40 @@ LOG = logging.getLogger(__name__)
METRICS = metrics_utils.get_metrics_logger(__name__)
# This dictionary is used to map boot device names between two (2) name
# spaces. The name spaces are:
#
# 1) ironic boot devices
# 2) iDRAC boot sources
#
# Mapping can be performed in both directions.
#
# The keys are ironic boot device types. Each value is a list of strings
# that appear in the identifiers of iDRAC boot sources.
#
# The iDRAC represents boot sources with class DCIM_BootSourceSetting
# [1]. Each instance of that class contains a unique identifier, which
# is called an instance identifier, InstanceID,
#
# An InstanceID contains the Fully Qualified Device Descriptor (FQDD) of
# the physical device that hosts the boot source [2].
#
# [1] "Dell EMC BIOS and Boot Management Profile", Version 4.0.0, July
# 10, 2017, Section 7.2 "Boot Management", pp. 44-47 --
# http://en.community.dell.com/techcenter/extras/m/white_papers/20444495/download
# [2] "Lifecycle Controller Version 3.15.15.15 User's Guide", Dell EMC,
# 2017, Table 13, "Easy-to-use Names of System Components", pp. 71-74 --
# http://topics-cdn.dell.com/pdf/idrac9-lifecycle-controller-v3.15.15.15_users-guide2_en-us.pdf
_BOOT_DEVICES_MAP = {
boot_devices.DISK: 'HardDisk',
boot_devices.PXE: 'NIC',
boot_devices.CDROM: 'Optical',
boot_devices.DISK: ['AHCI', 'Disk', 'RAID'],
boot_devices.PXE: ['NIC'],
boot_devices.CDROM: ['Optical'],
}
# BootMode constants
PERSISTENT_BOOT_MODE = 'IPL'
NON_PERSISTENT_BOOT_MODE = 'OneTime'
_DRAC_BOOT_MODES = ['Bios', 'Uefi']
# BootMode constant
_NON_PERSISTENT_BOOT_MODE = 'OneTime'
def _get_boot_device(node, drac_boot_devices=None):
@ -54,19 +80,31 @@ def _get_boot_device(node, drac_boot_devices=None):
try:
boot_modes = client.list_boot_modes()
next_boot_modes = [mode.id for mode in boot_modes if mode.is_next]
if NON_PERSISTENT_BOOT_MODE in next_boot_modes:
next_boot_mode = NON_PERSISTENT_BOOT_MODE
if _NON_PERSISTENT_BOOT_MODE in next_boot_modes:
next_boot_mode = _NON_PERSISTENT_BOOT_MODE
else:
next_boot_mode = next_boot_modes[0]
if drac_boot_devices is None:
drac_boot_devices = client.list_boot_devices()
drac_boot_device = drac_boot_devices[next_boot_mode][0]
boot_device = next(key for (key, value) in _BOOT_DEVICES_MAP.items()
if value in drac_boot_device.id)
# It is possible for there to be no boot device.
boot_device = None
if next_boot_mode in drac_boot_devices:
drac_boot_device = drac_boot_devices[next_boot_mode][0]
for key, value in _BOOT_DEVICES_MAP.items():
for id_component in value:
if id_component in drac_boot_device.id:
boot_device = key
break
if boot_device:
break
return {'boot_device': boot_device,
'persistent': next_boot_mode == PERSISTENT_BOOT_MODE}
'persistent': next_boot_mode != _NON_PERSISTENT_BOOT_MODE}
except (drac_exceptions.BaseClientException, IndexError) as exc:
LOG.error(_LE('DRAC driver failed to get next boot mode for '
'node %(node_uuid)s. Reason: %(error)s.'),
@ -74,6 +112,60 @@ def _get_boot_device(node, drac_boot_devices=None):
raise exception.DracOperationError(error=exc)
def _get_next_persistent_boot_mode(node):
client = drac_common.get_drac_client(node)
try:
boot_modes = client.list_boot_modes()
except drac_exceptions.BaseClientException as exc:
LOG.error('DRAC driver failed to get next persistent boot mode for '
'node %(node_uuid)s. Reason: %(error)s',
{'node_uuid': node.uuid, 'error': exc})
raise exception.DracOperationError(error=exc)
next_persistent_boot_mode = None
for mode in boot_modes:
if mode.is_next and mode.id != _NON_PERSISTENT_BOOT_MODE:
next_persistent_boot_mode = mode.id
break
if not next_persistent_boot_mode:
message = _('List of boot modes, %(list_boot_modes)s, does not '
'contain a persistent mode') % {
'list_boot_modes': boot_modes}
LOG.error('DRAC driver failed to get next persistent boot mode for '
'node %(node_uuid)s. Reason: %(message)s',
{'node_uuid': node.uuid, 'message': message})
raise exception.DracOperationError(error=message)
return next_persistent_boot_mode
def _is_boot_order_flexibly_programmable(persistent, bios_settings):
return persistent and 'SetBootOrderFqdd1' in bios_settings
def _flexibly_program_boot_order(device, drac_boot_mode):
if device == boot_devices.DISK:
if drac_boot_mode == 'Bios':
bios_settings = {'SetBootOrderFqdd1': 'HardDisk.List.1-1'}
else:
# 'Uefi'
bios_settings = {
'SetBootOrderFqdd1': '*.*.*', # Disks, which are all else
'SetBootOrderFqdd2': 'NIC.*.*',
'SetBootOrderFqdd3': 'Optical.*.*',
'SetBootOrderFqdd4': 'Floppy.*.*',
}
elif device == boot_devices.PXE:
bios_settings = {'SetBootOrderFqdd1': 'NIC.*.*'}
else:
# boot_devices.CDROM
bios_settings = {'SetBootOrderFqdd1': 'Optical.*.*'}
return bios_settings
def set_boot_device(node, device, persistent=False):
"""Set the boot device for a node.
@ -102,16 +194,58 @@ def set_boot_device(node, device, persistent=False):
LOG.debug('DRAC already set to boot from %s', device)
return
drac_boot_device = next(drac_device.id for drac_device
in drac_boot_devices[PERSISTENT_BOOT_MODE]
if _BOOT_DEVICES_MAP[device] in drac_device.id)
persistent_boot_mode = _get_next_persistent_boot_mode(node)
if persistent:
boot_list = PERSISTENT_BOOT_MODE
drac_boot_device = None
for drac_device in drac_boot_devices[persistent_boot_mode]:
for id_component in _BOOT_DEVICES_MAP[device]:
if id_component in drac_device.id:
drac_boot_device = drac_device.id
break
if drac_boot_device:
break
if drac_boot_device:
if persistent:
boot_list = persistent_boot_mode
else:
boot_list = _NON_PERSISTENT_BOOT_MODE
client.change_boot_device_order(boot_list, drac_boot_device)
else:
boot_list = NON_PERSISTENT_BOOT_MODE
# No DRAC boot device of the type requested by the argument
# 'device' is present. This is normal for UEFI boot mode,
# following deployment's writing of the operating system to
# disk. It can also occur when a server has not been
# powered on after a new boot device has been installed.
#
# If the boot order is flexibly programmable, use that to
# attempt to detect and boot from a device of the requested
# type during the next boot. That avoids the need for an
# extra reboot. Otherwise, this function cannot satisfy the
# request, because it was called with an invalid device.
bios_settings = client.list_bios_settings(by_name=True)
if _is_boot_order_flexibly_programmable(persistent, bios_settings):
drac_boot_mode = bios_settings['BootMode'].current_value
if drac_boot_mode not in _DRAC_BOOT_MODES:
message = _("DRAC reported unknown boot mode "
"'%(drac_boot_mode)s'") % {
'drac_boot_mode': drac_boot_mode}
LOG.error('DRAC driver failed to change boot device order '
'for node %(node_uuid)s. Reason: %(message)s.',
{'node_uuid': node.uuid, 'message': message})
raise exception.DracOperationError(error=message)
flexibly_program_settings = _flexibly_program_boot_order(
device, drac_boot_mode)
client.set_bios_settings(flexibly_program_settings)
else:
raise exception.InvalidParameterValue(
_("set_boot_device called with invalid device "
"'%(device)s' for node %(node_id)s.") %
{'device': device, 'node_id': node.uuid})
client.change_boot_device_order(boot_list, drac_boot_device)
client.commit_pending_bios_changes()
except drac_exceptions.BaseClientException as exc:
LOG.error(_LE('DRAC driver failed to change boot device order for '

View File

@ -2,6 +2,7 @@
#
# Copyright 2014 Red Hat, Inc.
# All Rights Reserved.
# Copyright (c) 2017-2018 Dell Inc. or its subsidiaries.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -20,6 +21,7 @@ Test class for DRAC management interface
"""
import mock
from oslo_utils import importutils
import ironic.common.boot_devices
from ironic.common import exception
@ -33,6 +35,8 @@ from ironic.tests.unit.db import utils as db_utils
from ironic.tests.unit.drivers.modules.drac import utils as test_utils
from ironic.tests.unit.objects import utils as obj_utils
dracclient_exceptions = importutils.try_import('dracclient.exceptions')
INFO_DICT = db_utils.get_test_drac_info()
@ -58,29 +62,44 @@ class DracManagementInternalMethodsTestCase(db_base.DbTestCase):
driver='fake_drac',
driver_info=INFO_DICT)
self.boot_device_pxe = {
boot_device_ipl_pxe = {
'id': 'BIOS.Setup.1-1#BootSeq#NIC.Embedded.1-1-1',
'boot_mode': 'IPL',
'current_assigned_sequence': 0,
'pending_assigned_sequence': 0,
'bios_boot_string': 'Embedded NIC 1 Port 1 Partition 1'}
self.boot_device_disk = {
boot_device_ipl_disk = {
'id': 'BIOS.Setup.1-1#BootSeq#HardDisk.List.1-1',
'boot_mode': 'IPL',
'current_assigned_sequence': 1,
'pending_assigned_sequence': 1,
'bios_boot_string': 'Hard drive C: BootSeq'}
self.ipl_boot_devices = [
test_utils.dict_to_namedtuple(values=self.boot_device_pxe),
test_utils.dict_to_namedtuple(values=self.boot_device_disk)]
self.boot_devices = {'IPL': self.ipl_boot_devices,
'OneTime': self.ipl_boot_devices}
ipl_boot_device_namedtuples = [
test_utils.dict_to_namedtuple(values=boot_device_ipl_pxe),
test_utils.dict_to_namedtuple(values=boot_device_ipl_disk)]
ipl_boot_devices = {'IPL': ipl_boot_device_namedtuples,
'OneTime': ipl_boot_device_namedtuples}
boot_device_uefi_pxe = {
'id': 'UEFI:BIOS.Setup.1-1#UefiBootSeq#NIC.PxeDevice.1-1',
'boot_mode': 'UEFI',
'current_assigned_sequence': 0,
'pending_assigned_sequence': 0,
'bios_boot_string':
'PXE Device 1: Integrated NIC 1 Port 1 Partition 1'}
uefi_boot_device_namedtuples = [
test_utils.dict_to_namedtuple(values=boot_device_uefi_pxe)]
uefi_boot_devices = {'UEFI': uefi_boot_device_namedtuples,
'OneTime': uefi_boot_device_namedtuples}
self.boot_devices = {'IPL': ipl_boot_devices,
'UEFI': uefi_boot_devices}
def test__get_boot_device(self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_boot_modes.return_value = self.boot_modes('IPL')
mock_client.list_boot_devices.return_value = self.boot_devices
mock_client.list_boot_devices.return_value = self.boot_devices['IPL']
boot_device = drac_mgmt._get_boot_device(self.node)
@ -96,7 +115,7 @@ class DracManagementInternalMethodsTestCase(db_base.DbTestCase):
# persistent boot modes
mock_client.list_boot_modes.return_value = self.boot_modes('IPL',
'OneTime')
mock_client.list_boot_devices.return_value = self.boot_devices
mock_client.list_boot_devices.return_value = self.boot_devices['IPL']
boot_device = drac_mgmt._get_boot_device(self.node)
@ -105,6 +124,20 @@ class DracManagementInternalMethodsTestCase(db_base.DbTestCase):
mock_client.list_boot_modes.assert_called_once_with()
mock_client.list_boot_devices.assert_called_once_with()
def test__get_boot_device_with_no_boot_device(self,
mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_boot_modes.return_value = self.boot_modes('IPL')
mock_client.list_boot_devices.return_value = {}
boot_device = drac_mgmt._get_boot_device(self.node)
expected_boot_device = {'boot_device': None, 'persistent': True}
self.assertEqual(expected_boot_device, boot_device)
mock_client.list_boot_modes.assert_called_once_with()
mock_client.list_boot_devices.assert_called_once_with()
def test__get_boot_device_with_empty_boot_mode_list(self,
mock_get_drac_client):
mock_client = mock.Mock()
@ -114,19 +147,124 @@ class DracManagementInternalMethodsTestCase(db_base.DbTestCase):
self.assertRaises(exception.DracOperationError,
drac_mgmt._get_boot_device, self.node)
def test__get_next_persistent_boot_mode(self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_boot_modes.return_value = self.boot_modes('IPL')
boot_mode = drac_mgmt._get_next_persistent_boot_mode(self.node)
mock_get_drac_client.assert_called_once_with(self.node)
mock_client.list_boot_modes.assert_called_once_with()
expected_boot_mode = 'IPL'
self.assertEqual(expected_boot_mode, boot_mode)
def test__get_next_persistent_boot_mode_with_non_persistent_boot_mode(
self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_boot_modes.return_value = self.boot_modes('IPL',
'OneTime')
boot_mode = drac_mgmt._get_next_persistent_boot_mode(self.node)
mock_get_drac_client.assert_called_once_with(self.node)
mock_client.list_boot_modes.assert_called_once_with()
expected_boot_mode = 'IPL'
self.assertEqual(expected_boot_mode, boot_mode)
def test__get_next_persistent_boot_mode_list_boot_modes_fail(
self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
exc = dracclient_exceptions.BaseClientException('boom')
mock_client.list_boot_modes.side_effect = exc
self.assertRaises(exception.DracOperationError,
drac_mgmt._get_next_persistent_boot_mode, self.node)
mock_get_drac_client.assert_called_once_with(self.node)
mock_client.list_boot_modes.assert_called_once_with()
def test__get_next_persistent_boot_mode_with_empty_boot_mode_list(
self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_boot_modes.return_value = []
self.assertRaises(exception.DracOperationError,
drac_mgmt._get_next_persistent_boot_mode, self.node)
mock_get_drac_client.assert_called_once_with(self.node)
mock_client.list_boot_modes.assert_called_once_with()
def test__is_boot_order_flexibly_programmable(self, mock_get_drac_client):
self.assertTrue(drac_mgmt._is_boot_order_flexibly_programmable(
persistent=True, bios_settings={'SetBootOrderFqdd1': ()}))
def test__is_boot_order_flexibly_programmable_not_persistent(
self, mock_get_drac_client):
self.assertFalse(drac_mgmt._is_boot_order_flexibly_programmable(
persistent=False, bios_settings={'SetBootOrderFqdd1': ()}))
def test__is_boot_order_flexibly_programmable_with_no_bios_setting(
self, mock_get_drac_client):
self.assertFalse(drac_mgmt._is_boot_order_flexibly_programmable(
persistent=True, bios_settings={}))
def test__flexibly_program_boot_order_for_disk_and_bios(
self, mock_get_drac_client):
settings = drac_mgmt._flexibly_program_boot_order(
ironic.common.boot_devices.DISK, drac_boot_mode='Bios')
expected_settings = {'SetBootOrderFqdd1': 'HardDisk.List.1-1'}
self.assertEqual(expected_settings, settings)
def test__flexibly_program_boot_order_for_disk_and_uefi(
self, mock_get_drac_client):
settings = drac_mgmt._flexibly_program_boot_order(
ironic.common.boot_devices.DISK, drac_boot_mode='Uefi')
expected_settings = {
'SetBootOrderFqdd1': '*.*.*',
'SetBootOrderFqdd2': 'NIC.*.*',
'SetBootOrderFqdd3': 'Optical.*.*',
'SetBootOrderFqdd4': 'Floppy.*.*',
}
self.assertEqual(expected_settings, settings)
def test__flexibly_program_boot_order_for_pxe(self, mock_get_drac_client):
settings = drac_mgmt._flexibly_program_boot_order(
ironic.common.boot_devices.PXE, drac_boot_mode='Uefi')
expected_settings = {'SetBootOrderFqdd1': 'NIC.*.*'}
self.assertEqual(expected_settings, settings)
def test__flexibly_program_boot_order_for_cdrom(self,
mock_get_drac_client):
settings = drac_mgmt._flexibly_program_boot_order(
ironic.common.boot_devices.CDROM, drac_boot_mode='Uefi')
expected_settings = {'SetBootOrderFqdd1': 'Optical.*.*'}
self.assertEqual(expected_settings, settings)
@mock.patch.object(drac_mgmt, '_get_next_persistent_boot_mode',
spec_set=True, autospec=True)
@mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
autospec=True)
@mock.patch.object(drac_job, 'validate_job_queue', spec_set=True,
autospec=True)
def test_set_boot_device(self, mock_validate_job_queue,
mock__get_boot_device, mock_get_drac_client):
mock__get_boot_device,
mock__get_next_persistent_boot_mode,
mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_boot_modes.return_value = self.boot_modes('IPL')
mock_client.list_boot_devices.return_value = self.boot_devices
mock_client.list_boot_devices.return_value = self.boot_devices['IPL']
boot_device = {'boot_device': ironic.common.boot_devices.DISK,
'persistent': True}
mock__get_boot_device.return_value = boot_device
mock__get_next_persistent_boot_mode.return_value = 'IPL'
boot_device = drac_mgmt.set_boot_device(
self.node, ironic.common.boot_devices.PXE, persistent=False)
@ -134,39 +272,172 @@ class DracManagementInternalMethodsTestCase(db_base.DbTestCase):
mock_validate_job_queue.assert_called_once_with(self.node)
mock_client.change_boot_device_order.assert_called_once_with(
'OneTime', 'BIOS.Setup.1-1#BootSeq#NIC.Embedded.1-1-1')
self.assertEqual(0, mock_client.set_bios_settings.call_count)
mock_client.commit_pending_bios_changes.assert_called_once_with()
@mock.patch.object(drac_mgmt, '_get_next_persistent_boot_mode',
spec_set=True, autospec=True)
@mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
autospec=True)
@mock.patch.object(drac_job, 'validate_job_queue', spec_set=True,
autospec=True)
def test_set_boot_device_called_with_no_change(
self, mock_validate_job_queue, mock__get_boot_device,
mock_get_drac_client):
mock__get_next_persistent_boot_mode, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_boot_modes.return_value = self.boot_modes('IPL')
mock_client.list_boot_devices.return_value = self.boot_devices
mock_client.list_boot_devices.return_value = self.boot_devices['IPL']
boot_device = {'boot_device': ironic.common.boot_devices.PXE,
'persistent': True}
mock__get_boot_device.return_value = boot_device
mock__get_next_persistent_boot_mode.return_value = 'IPL'
boot_device = drac_mgmt.set_boot_device(
self.node, ironic.common.boot_devices.PXE, persistent=True)
mock_validate_job_queue.assert_called_once_with(self.node)
self.assertEqual(0, mock_client.change_boot_device_order.call_count)
self.assertEqual(0, mock_client.set_bios_settings.call_count)
self.assertEqual(0, mock_client.commit_pending_bios_changes.call_count)
@mock.patch.object(drac_mgmt, '_flexibly_program_boot_order',
spec_set=True, autospec=True)
@mock.patch.object(drac_mgmt, '_is_boot_order_flexibly_programmable',
spec_set=True, autospec=True)
@mock.patch.object(drac_mgmt, '_get_next_persistent_boot_mode',
spec_set=True, autospec=True)
@mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
autospec=True)
@mock.patch.object(drac_job, 'validate_job_queue', spec_set=True,
autospec=True)
def test_set_boot_device_with_invalid_job_queue(
def test_set_boot_device_called_with_no_drac_boot_device(
self, mock_validate_job_queue, mock__get_boot_device,
mock__get_next_persistent_boot_mode,
mock__is_boot_order_flexibly_programmable,
mock__flexibly_program_boot_order,
mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_boot_devices.return_value = self.boot_devices['UEFI']
boot_device = {'boot_device': ironic.common.boot_devices.PXE,
'persistent': False}
mock__get_boot_device.return_value = boot_device
mock__get_next_persistent_boot_mode.return_value = 'UEFI'
settings = [
{
'name': 'BootMode',
'instance_id': 'BIOS.Setup.1-1:BootMode',
'current_value': 'Uefi',
'pending_value': None,
'read_only': False,
'possible_values': ['Bios', 'Uefi']
},
]
bios_settings = {
s['name']: test_utils.dict_to_namedtuple(
values=s) for s in settings}
mock_client.list_bios_settings.return_value = bios_settings
mock__is_boot_order_flexibly_programmable.return_value = True
flexibly_program_settings = {
'SetBootOrderFqdd1': '*.*.*',
'SetBootOrderFqdd2': 'NIC.*.*',
'SetBootOrderFqdd3': 'Optical.*.*',
'SetBootOrderFqdd4': 'Floppy.*.*',
}
mock__flexibly_program_boot_order.return_value = \
flexibly_program_settings
drac_mgmt.set_boot_device(self.node, ironic.common.boot_devices.DISK,
persistent=True)
mock_validate_job_queue.assert_called_once_with(self.node)
self.assertEqual(0, mock_client.change_boot_device_order.call_count)
mock_client.set_bios_settings.assert_called_once_with(
flexibly_program_settings)
mock_client.commit_pending_bios_changes.assert_called_once_with()
@mock.patch.object(drac_mgmt, '_is_boot_order_flexibly_programmable',
spec_set=True, autospec=True)
@mock.patch.object(drac_mgmt, '_get_next_persistent_boot_mode',
spec_set=True, autospec=True)
@mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
autospec=True)
@mock.patch.object(drac_job, 'validate_job_queue', spec_set=True,
autospec=True)
def test_set_boot_device_called_with_not_flexibly_programmable(
self, mock_validate_job_queue, mock__get_boot_device,
mock__get_next_persistent_boot_mode,
mock__is_boot_order_flexibly_programmable,
mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_boot_devices.return_value = self.boot_devices['UEFI']
boot_device = {'boot_device': ironic.common.boot_devices.PXE,
'persistent': False}
mock__get_boot_device.return_value = boot_device
mock__get_next_persistent_boot_mode.return_value = 'UEFI'
mock__is_boot_order_flexibly_programmable.return_value = False
self.assertRaises(exception.InvalidParameterValue,
drac_mgmt.set_boot_device, self.node,
ironic.common.boot_devices.CDROM, persistent=False)
mock_validate_job_queue.assert_called_once_with(self.node)
self.assertEqual(0, mock_client.change_boot_device_order.call_count)
self.assertEqual(0, mock_client.set_bios_settings.call_count)
self.assertEqual(0, mock_client.commit_pending_bios_changes.call_count)
@mock.patch.object(drac_mgmt, '_is_boot_order_flexibly_programmable',
spec_set=True, autospec=True)
@mock.patch.object(drac_mgmt, '_get_next_persistent_boot_mode',
spec_set=True, autospec=True)
@mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
autospec=True)
@mock.patch.object(drac_job, 'validate_job_queue', spec_set=True,
autospec=True)
def test_set_boot_device_called_with_unknown_boot_mode(
self, mock_validate_job_queue, mock__get_boot_device,
mock__get_next_persistent_boot_mode,
mock__is_boot_order_flexibly_programmable,
mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_boot_devices.return_value = self.boot_devices['UEFI']
boot_device = {'boot_device': ironic.common.boot_devices.PXE,
'persistent': False}
mock__get_boot_device.return_value = boot_device
mock__get_next_persistent_boot_mode.return_value = 'UEFI'
settings = [
{
'name': 'BootMode',
'instance_id': 'BIOS.Setup.1-1:BootMode',
'current_value': 'Bad',
'pending_value': None,
'read_only': False,
'possible_values': ['Bios', 'Uefi', 'Bad']
},
]
bios_settings = {
s['name']: test_utils.dict_to_namedtuple(
values=s) for s in settings}
mock_client.list_bios_settings.return_value = bios_settings
mock__is_boot_order_flexibly_programmable.return_value = True
self.assertRaises(exception.DracOperationError,
drac_mgmt.set_boot_device, self.node,
ironic.common.boot_devices.DISK, persistent=True)
mock_validate_job_queue.assert_called_once_with(self.node)
self.assertEqual(0, mock_client.change_boot_device_order.call_count)
self.assertEqual(0, mock_client.set_bios_settings.call_count)
self.assertEqual(0, mock_client.commit_pending_bios_changes.call_count)
@mock.patch.object(drac_job, 'validate_job_queue', spec_set=True,
autospec=True)
def test_set_boot_device_with_invalid_job_queue(
self, mock_validate_job_queue, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_validate_job_queue.side_effect = exception.DracOperationError(
'boom')
@ -175,6 +446,7 @@ class DracManagementInternalMethodsTestCase(db_base.DbTestCase):
ironic.common.boot_devices.PXE, persistent=True)
self.assertEqual(0, mock_client.change_boot_device_order.call_count)
self.assertEqual(0, mock_client.set_bios_settings.call_count)
self.assertEqual(0, mock_client.commit_pending_bios_changes.call_count)

View File

@ -0,0 +1,11 @@
---
fixes:
- |
Fixes an issue that caused the integrated Dell Remote Access
Controller (iDRAC) ``management`` hardware interface implementation,
``idrac``, to fail to boot nodes in Unified Extensible Firmware Interface
(UEFI) boot mode. That interface is supported by the ``idrac`` hardware
type. The issue is resolved for Dell EMC PowerEdge 13th and 14th generation
servers. It is not resolved for PowerEdge 12th generation and earlier
servers. For more information, see `story 1656841
<https://storyboard.openstack.org/#!/story/1656841>`_.