Add iPXE boot interface to 'ilo' hardware type

This commit adds new boot interface 'ilo-ipxe' to support booting
of iPXE instances using 'ilo' hardware type.

Change-Id: I3abebc77cbc57344a74759e0a8b99e8cacac6f6b
Story: 2006408
Task: 36292
This commit is contained in:
Shivanand Tendulker 2019-08-16 02:28:32 -04:00
parent ce163996ce
commit 1e3b684037
5 changed files with 304 additions and 1 deletions

View File

@ -38,7 +38,7 @@ class IloHardware(generic.GenericHardware):
@property
def supported_boot_interfaces(self):
"""List of supported boot interfaces."""
return [boot.IloVirtualMediaBoot, boot.IloPXEBoot]
return [boot.IloVirtualMediaBoot, boot.IloPXEBoot, boot.IloiPXEBoot]
@property
def supported_bios_interfaces(self):

View File

@ -38,6 +38,7 @@ from ironic.drivers import base
from ironic.drivers.modules import boot_mode_utils
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.ilo import common as ilo_common
from ironic.drivers.modules import ipxe
from ironic.drivers.modules import pxe
LOG = logging.getLogger(__name__)
@ -749,3 +750,101 @@ class IloPXEBoot(pxe.PXEBoot):
# Volume boot in BIOS boot mode is handled using
# PXE boot interface
super(IloPXEBoot, self).clean_up_instance(task)
class IloiPXEBoot(ipxe.iPXEBoot):
@METRICS.timer('IloiPXEBoot.prepare_ramdisk')
def prepare_ramdisk(self, task, ramdisk_params):
"""Prepares the boot of Ironic ramdisk using PXE.
This method prepares the boot of the deploy or rescue ramdisk after
reading relevant information from the node's driver_info and
instance_info.
:param task: a task from TaskManager.
:param ramdisk_params: the parameters to be passed to the ramdisk.
:returns: None
:raises: MissingParameterValue, if some information is missing in
node's driver_info or instance_info.
:raises: InvalidParameterValue, if some information provided is
invalid.
:raises: IronicException, if some power or set boot boot device
operation failed on the node.
:raises: IloOperationError, if some operation on iLO failed.
"""
if task.node.provision_state in (states.DEPLOYING, states.RESCUING,
states.CLEANING):
prepare_node_for_deploy(task)
super(IloiPXEBoot, self).prepare_ramdisk(task, ramdisk_params)
@METRICS.timer('IloiPXEBoot.prepare_instance')
def prepare_instance(self, task):
"""Prepares the boot of instance.
This method prepares the boot of the instance after reading
relevant information from the node's instance_info. In case of netboot,
it updates the dhcp entries and switches the PXE config. In case of
localboot, it cleans up the PXE config.
In case of 'boot from volume', it updates the iSCSI info onto iLO and
sets the node to boot from 'UefiTarget' boot device.
:param task: a task from TaskManager.
:returns: None
:raises: IloOperationError, if some operation on iLO failed.
"""
# Set boot mode
ilo_common.update_boot_mode(task)
# Need to enable secure boot, if being requested
ilo_common.update_secure_boot_mode(task, True)
boot_mode = boot_mode_utils.get_boot_mode(task.node)
if deploy_utils.is_iscsi_boot(task) and boot_mode == 'uefi':
# Need to set 'ilo_uefi_iscsi_boot' param for clean up
driver_internal_info = task.node.driver_internal_info
driver_internal_info['ilo_uefi_iscsi_boot'] = True
task.node.driver_internal_info = driver_internal_info
task.node.save()
# It will set iSCSI info onto iLO
task.driver.management.set_iscsi_boot_target(task)
manager_utils.node_set_boot_device(task, boot_devices.ISCSIBOOT,
persistent=True)
else:
# Volume boot in BIOS boot mode is handled using
# PXE boot interface
super(IloiPXEBoot, self).prepare_instance(task)
@METRICS.timer('IloiPXEBoot.clean_up_instance')
def clean_up_instance(self, task):
"""Cleans up the boot of instance.
This method cleans up the PXE environment that was setup for booting
the instance. It unlinks the instance kernel/ramdisk in the node's
directory in tftproot and removes it's PXE config.
In case of UEFI iSCSI booting, it cleans up iSCSI target information
from the node.
:param task: a task from TaskManager.
:returns: None
:raises: IloOperationError, if some operation on iLO failed.
"""
manager_utils.node_power_action(task, states.POWER_OFF)
disable_secure_boot_if_supported(task)
driver_internal_info = task.node.driver_internal_info
if (deploy_utils.is_iscsi_boot(task)
and task.node.driver_internal_info.get('ilo_uefi_iscsi_boot')):
# It will clear iSCSI info from iLO in case of booting from
# volume in UEFI boot mode
task.driver.management.clear_iscsi_boot_target(task)
driver_internal_info.pop('ilo_uefi_iscsi_boot', None)
task.node.driver_internal_info = driver_internal_info
task.node.save()
else:
# Volume boot in BIOS boot mode is handled using
# PXE boot interface
super(IloiPXEBoot, self).clean_up_instance(task)

View File

@ -36,6 +36,7 @@ from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.ilo import boot as ilo_boot
from ironic.drivers.modules.ilo import common as ilo_common
from ironic.drivers.modules.ilo import management as ilo_management
from ironic.drivers.modules import ipxe
from ironic.drivers.modules import pxe
from ironic.drivers.modules.storage import noop as noop_storage
from ironic.drivers import utils as driver_utils
@ -1392,3 +1393,186 @@ class IloPXEBootTestCase(test_common.BaseIloTest):
update_secure_boot_mode_mock.assert_called_once_with(task, True)
self.assertTrue(task.node.driver_internal_info.get(
'ilo_uefi_iscsi_boot'))
@mock.patch.object(ipxe.iPXEBoot, '__init__', lambda self: None)
class IloiPXEBootTestCase(test_common.BaseIloTest):
boot_interface = 'ilo-ipxe'
def setUp(self):
super(IloiPXEBootTestCase, self).setUp()
self.config(enabled_boot_interfaces=['ilo-ipxe'])
@mock.patch.object(ilo_boot, 'prepare_node_for_deploy', spec_set=True,
autospec=True)
@mock.patch.object(ipxe.iPXEBoot, 'prepare_ramdisk', spec_set=True,
autospec=True)
def _test_prepare_ramdisk_needs_node_prep(self, pxe_prepare_ramdisk_mock,
prepare_node_mock, prov_state):
self.node.provision_state = prov_state
self.node.save()
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
self.assertIsNone(
task.driver.boot.prepare_ramdisk(task, None))
prepare_node_mock.assert_called_once_with(task)
pxe_prepare_ramdisk_mock.assert_called_once_with(
mock.ANY, task, None)
def test_prepare_ramdisk_in_deploying(self):
self._test_prepare_ramdisk_needs_node_prep(prov_state=states.DEPLOYING)
def test_prepare_ramdisk_in_rescuing(self):
self._test_prepare_ramdisk_needs_node_prep(prov_state=states.RESCUING)
def test_prepare_ramdisk_in_cleaning(self):
self._test_prepare_ramdisk_needs_node_prep(prov_state=states.CLEANING)
@mock.patch.object(deploy_utils, 'is_iscsi_boot',
spec_set=True, autospec=True)
@mock.patch.object(ilo_common, 'update_secure_boot_mode', spec_set=True,
autospec=True)
@mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
autospec=True)
@mock.patch.object(ipxe.iPXEBoot, 'clean_up_instance', spec_set=True,
autospec=True)
def test_clean_up_instance(self, pxe_cleanup_mock, node_power_mock,
update_secure_boot_mode_mock,
is_iscsi_boot_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.boot.clean_up_instance(task)
is_iscsi_boot_mock.return_value = False
node_power_mock.assert_called_once_with(task, states.POWER_OFF)
update_secure_boot_mode_mock.assert_called_once_with(task, False)
pxe_cleanup_mock.assert_called_once_with(mock.ANY, task)
@mock.patch.object(deploy_utils, 'is_iscsi_boot',
spec_set=True, autospec=True)
@mock.patch.object(ilo_common, 'update_secure_boot_mode', spec_set=True,
autospec=True)
@mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
autospec=True)
@mock.patch.object(ipxe.iPXEBoot, 'clean_up_instance', spec_set=True,
autospec=True)
def test_clean_up_instance_boot_from_volume_bios(
self, pxe_cleanup_mock, node_power_mock,
update_secure_boot_mode_mock, is_iscsi_boot_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.boot.clean_up_instance(task)
is_iscsi_boot_mock.return_value = True
node_power_mock.assert_called_once_with(task, states.POWER_OFF)
update_secure_boot_mode_mock.assert_called_once_with(task, False)
pxe_cleanup_mock.assert_called_once_with(mock.ANY, task)
@mock.patch.object(deploy_utils, 'is_iscsi_boot',
spec_set=True, autospec=True)
@mock.patch.object(ilo_management.IloManagement, 'clear_iscsi_boot_target',
spec_set=True, autospec=True)
@mock.patch.object(ilo_common, 'update_secure_boot_mode', spec_set=True,
autospec=True)
@mock.patch.object(manager_utils, 'node_power_action', spec_set=True,
autospec=True)
def test_clean_up_instance_boot_from_volume(self, node_power_mock,
update_secure_boot_mode_mock,
clear_iscsi_boot_target_mock,
is_iscsi_boot_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
driver_internal_info = task.node.driver_internal_info
driver_internal_info['ilo_uefi_iscsi_boot'] = True
task.node.driver_internal_info = driver_internal_info
task.node.save()
is_iscsi_boot_mock.return_value = True
task.driver.boot.clean_up_instance(task)
clear_iscsi_boot_target_mock.assert_called_once_with(mock.ANY,
task)
node_power_mock.assert_called_once_with(task, states.POWER_OFF)
update_secure_boot_mode_mock.assert_called_once_with(task, False)
self.assertIsNone(task.node.driver_internal_info.get(
'ilo_uefi_iscsi_boot'))
@mock.patch.object(deploy_utils, 'is_iscsi_boot',
spec_set=True, autospec=True)
@mock.patch.object(boot_mode_utils, 'get_boot_mode_for_deploy',
spec_set=True, autospec=True)
@mock.patch.object(ilo_common, 'update_secure_boot_mode', spec_set=True,
autospec=True)
@mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
autospec=True)
@mock.patch.object(ipxe.iPXEBoot, 'prepare_instance', spec_set=True,
autospec=True)
def test_prepare_instance(self, pxe_prepare_instance_mock,
update_boot_mode_mock,
update_secure_boot_mode_mock,
get_boot_mode_mock,
is_iscsi_boot_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.boot.prepare_instance(task)
is_iscsi_boot_mock.return_value = False
get_boot_mode_mock.return_value = 'uefi'
update_boot_mode_mock.assert_called_once_with(task)
update_secure_boot_mode_mock.assert_called_once_with(task, True)
pxe_prepare_instance_mock.assert_called_once_with(mock.ANY, task)
self.assertIsNone(task.node.driver_internal_info.get(
'ilo_uefi_iscsi_boot'))
@mock.patch.object(deploy_utils, 'is_iscsi_boot',
spec_set=True, autospec=True)
@mock.patch.object(boot_mode_utils, 'get_boot_mode_for_deploy',
spec_set=True, autospec=True)
@mock.patch.object(ilo_common, 'update_secure_boot_mode', spec_set=True,
autospec=True)
@mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
autospec=True)
@mock.patch.object(ipxe.iPXEBoot, 'prepare_instance', spec_set=True,
autospec=True)
def test_prepare_instance_bios(self, pxe_prepare_instance_mock,
update_boot_mode_mock,
update_secure_boot_mode_mock,
get_boot_mode_mock,
is_iscsi_boot_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.boot.prepare_instance(task)
is_iscsi_boot_mock.return_value = False
get_boot_mode_mock.return_value = 'bios'
update_boot_mode_mock.assert_called_once_with(task)
update_secure_boot_mode_mock.assert_called_once_with(task, True)
pxe_prepare_instance_mock.assert_called_once_with(mock.ANY, task)
self.assertIsNone(task.node.driver_internal_info.get(
'ilo_uefi_iscsi_boot'))
@mock.patch.object(deploy_utils, 'is_iscsi_boot',
spec_set=True, autospec=True)
@mock.patch.object(boot_mode_utils, 'get_boot_mode_for_deploy',
spec_set=True, autospec=True)
@mock.patch.object(ilo_management.IloManagement, 'set_iscsi_boot_target',
spec_set=True, autospec=True)
@mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
autospec=True)
@mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True,
autospec=True)
@mock.patch.object(ilo_common, 'update_secure_boot_mode', spec_set=True,
autospec=True)
def test_prepare_instance_boot_from_volume(
self, update_secure_boot_mode_mock,
update_boot_mode_mock, set_boot_device_mock,
set_iscsi_boot_target_mock, get_boot_mode_mock,
is_iscsi_boot_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
is_iscsi_boot_mock.return_value = True
get_boot_mode_mock.return_value = 'uefi'
task.driver.boot.prepare_instance(task)
set_iscsi_boot_target_mock.assert_called_once_with(mock.ANY, task)
set_boot_device_mock.assert_called_once_with(
task, boot_devices.ISCSIBOOT, persistent=True)
update_boot_mode_mock.assert_called_once_with(task)
update_secure_boot_mode_mock.assert_called_once_with(task, True)
self.assertTrue(task.node.driver_internal_info.get(
'ilo_uefi_iscsi_boot'))

View File

@ -0,0 +1,19 @@
---
features:
- |
Adds an ``ilo-ipxe`` boot interface to ``ilo`` hardware type which
allows for instance level iPXE enablement as opposed to
conductor-wide enablement of iPXE.
To perform iPXE boot with ``ilo-ipxe`` boot interface:
* Add ``ilo-ipxe`` to ``enabled_boot_interfaces`` in ``ironic.conf``
* Set up TFTP & HTTP server using `Ironic document on iPXE boot
configuration
<https://docs.openstack.org/ironic/latest/install/configure-pxe.html>`_
* Create/Set baremetal node with ``--boot-interface ilo-ipxe``
fixes:
- |
From Stein release, ``[pxe]ipxe_enabled`` option has been deprecated.
The ``ilo`` hardware type supports iPXE boot through
``[pxe]ipxe_enabled`` option. To cope with this incompatibility,
``ilo`` hardware type has added new ``ilo-ipxe`` boot interface.

View File

@ -64,6 +64,7 @@ ironic.hardware.interfaces.bios =
ironic.hardware.interfaces.boot =
fake = ironic.drivers.modules.fake:FakeBoot
ilo-pxe = ironic.drivers.modules.ilo.boot:IloPXEBoot
ilo-ipxe = ironic.drivers.modules.ilo.boot:IloiPXEBoot
ilo-virtual-media = ironic.drivers.modules.ilo.boot:IloVirtualMediaBoot
ipxe = ironic.drivers.modules.ipxe:iPXEBoot
irmc-pxe = ironic.drivers.modules.irmc.boot:IRMCPXEBoot