summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2019-08-20 16:04:26 +0000
committerGerrit Code Review <review@openstack.org>2019-08-20 16:04:26 +0000
commited5f8a67af207c1caf5bde0c4e596b4ad0124b72 (patch)
tree96b4c7fbc79db74f5dcc77a9302d05d9b31f8857
parenta447a10b12efed2e989ed61de5d0d1562a2919ea (diff)
parent1e3b684037059ef931d11d02e1e5ec04f7c46aee (diff)
downloadironic-ed5f8a67af207c1caf5bde0c4e596b4ad0124b72.tar.gz
Merge "Add iPXE boot interface to 'ilo' hardware type"
-rw-r--r--ironic/drivers/ilo.py2
-rw-r--r--ironic/drivers/modules/ilo/boot.py99
-rw-r--r--ironic/tests/unit/drivers/modules/ilo/test_boot.py184
-rw-r--r--releasenotes/notes/adds-ilo-ipxe-boot-interface-4fc75292122db80d.yaml19
-rw-r--r--setup.cfg1
5 files changed, 304 insertions, 1 deletions
diff --git a/ironic/drivers/ilo.py b/ironic/drivers/ilo.py
index 3540c6944..36f094ddc 100644
--- a/ironic/drivers/ilo.py
+++ b/ironic/drivers/ilo.py
@@ -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):
diff --git a/ironic/drivers/modules/ilo/boot.py b/ironic/drivers/modules/ilo/boot.py
index 557a8d257..a15a516c4 100644
--- a/ironic/drivers/modules/ilo/boot.py
+++ b/ironic/drivers/modules/ilo/boot.py
@@ -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)
diff --git a/ironic/tests/unit/drivers/modules/ilo/test_boot.py b/ironic/tests/unit/drivers/modules/ilo/test_boot.py
index f85f79a08..384f5f43f 100644
--- a/ironic/tests/unit/drivers/modules/ilo/test_boot.py
+++ b/ironic/tests/unit/drivers/modules/ilo/test_boot.py
@@ -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'))
diff --git a/releasenotes/notes/adds-ilo-ipxe-boot-interface-4fc75292122db80d.yaml b/releasenotes/notes/adds-ilo-ipxe-boot-interface-4fc75292122db80d.yaml
new file mode 100644
index 000000000..d24bbec5d
--- /dev/null
+++ b/releasenotes/notes/adds-ilo-ipxe-boot-interface-4fc75292122db80d.yaml
@@ -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.
diff --git a/setup.cfg b/setup.cfg
index 5ce50f717..556c94218 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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