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:
parent
ce163996ce
commit
1e3b684037
@ -38,7 +38,7 @@ class IloHardware(generic.GenericHardware):
|
|||||||
@property
|
@property
|
||||||
def supported_boot_interfaces(self):
|
def supported_boot_interfaces(self):
|
||||||
"""List of supported boot interfaces."""
|
"""List of supported boot interfaces."""
|
||||||
return [boot.IloVirtualMediaBoot, boot.IloPXEBoot]
|
return [boot.IloVirtualMediaBoot, boot.IloPXEBoot, boot.IloiPXEBoot]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_bios_interfaces(self):
|
def supported_bios_interfaces(self):
|
||||||
|
@ -38,6 +38,7 @@ from ironic.drivers import base
|
|||||||
from ironic.drivers.modules import boot_mode_utils
|
from ironic.drivers.modules import boot_mode_utils
|
||||||
from ironic.drivers.modules import deploy_utils
|
from ironic.drivers.modules import deploy_utils
|
||||||
from ironic.drivers.modules.ilo import common as ilo_common
|
from ironic.drivers.modules.ilo import common as ilo_common
|
||||||
|
from ironic.drivers.modules import ipxe
|
||||||
from ironic.drivers.modules import pxe
|
from ironic.drivers.modules import pxe
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -749,3 +750,101 @@ class IloPXEBoot(pxe.PXEBoot):
|
|||||||
# Volume boot in BIOS boot mode is handled using
|
# Volume boot in BIOS boot mode is handled using
|
||||||
# PXE boot interface
|
# PXE boot interface
|
||||||
super(IloPXEBoot, self).clean_up_instance(task)
|
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)
|
||||||
|
@ -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 boot as ilo_boot
|
||||||
from ironic.drivers.modules.ilo import common as ilo_common
|
from ironic.drivers.modules.ilo import common as ilo_common
|
||||||
from ironic.drivers.modules.ilo import management as ilo_management
|
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 import pxe
|
||||||
from ironic.drivers.modules.storage import noop as noop_storage
|
from ironic.drivers.modules.storage import noop as noop_storage
|
||||||
from ironic.drivers import utils as driver_utils
|
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)
|
update_secure_boot_mode_mock.assert_called_once_with(task, True)
|
||||||
self.assertTrue(task.node.driver_internal_info.get(
|
self.assertTrue(task.node.driver_internal_info.get(
|
||||||
'ilo_uefi_iscsi_boot'))
|
'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'))
|
||||||
|
@ -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.
|
@ -64,6 +64,7 @@ ironic.hardware.interfaces.bios =
|
|||||||
ironic.hardware.interfaces.boot =
|
ironic.hardware.interfaces.boot =
|
||||||
fake = ironic.drivers.modules.fake:FakeBoot
|
fake = ironic.drivers.modules.fake:FakeBoot
|
||||||
ilo-pxe = ironic.drivers.modules.ilo.boot:IloPXEBoot
|
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
|
ilo-virtual-media = ironic.drivers.modules.ilo.boot:IloVirtualMediaBoot
|
||||||
ipxe = ironic.drivers.modules.ipxe:iPXEBoot
|
ipxe = ironic.drivers.modules.ipxe:iPXEBoot
|
||||||
irmc-pxe = ironic.drivers.modules.irmc.boot:IRMCPXEBoot
|
irmc-pxe = ironic.drivers.modules.irmc.boot:IRMCPXEBoot
|
||||||
|
Loading…
Reference in New Issue
Block a user