Implementation for UEFI iSCSI boot for ILO

This change adds new methods in management interface and enhance
boot interface of 'ilo' hardware type to support boot from iSCSI
volume in UEFI boot mode.

Change-Id: I585e0ef90f4397af1f09920c3a1bc47ddbcb1a97
Related-Bug: #1526861
This commit is contained in:
kesper 2017-05-26 07:15:13 +00:00 committed by paresh-sao
parent a8a57048eb
commit 8763637255
9 changed files with 567 additions and 43 deletions

View File

@ -235,9 +235,9 @@ Prerequisites
which contains a set of modules for managing HPE ProLiant hardware.
Install ``proliantutils`` module on the ironic conductor node. Minimum
version required is 2.4.1::
version required is 2.5.0::
$ pip install "proliantutils>=2.4.1"
$ pip install "proliantutils>=2.5.0"
* ``ipmitool`` command must be present on the service node(s) where
``ironic-conductor`` is running. On most distros, this is provided as part
@ -1812,7 +1812,7 @@ firmware components on the node. Refer to `SUM User Guide`_ to get more
information on SUM based firmware update.
``update_firmware_sum`` clean step requires the agent ramdisk with
``Proliant Hardware Manager`` from the proliantutils version 2.4.0 or higher.
``Proliant Hardware Manager`` from the proliantutils version 2.5.0 or higher.
See `DIB support for Proliant Hardware Manager`_ to create the agent ramdisk
with ``Proliant Hardware Manager``.

View File

@ -4,7 +4,7 @@
# python projects they should package as optional dependencies for Ironic.
# These are available on pypi
proliantutils>=2.4.1
proliantutils>=2.5.0
pysnmp
python-ironic-inspector-client>=1.5.0
python-oneviewclient<3.0.0,>=2.5.2

View File

@ -43,3 +43,6 @@ SAFE = 'safe'
WANBOOT = 'wanboot'
"Boot from Wide Area Network"
ISCSIBOOT = 'iscsiboot'
"Boot from iSCSI volume"

View File

@ -383,6 +383,8 @@ def disable_secure_boot_if_supported(task):
class IloVirtualMediaBoot(base.BootInterface):
capabilities = ['iscsi_volume_boot']
def get_properties(self):
return COMMON_PROPERTIES
@ -397,9 +399,13 @@ class IloVirtualMediaBoot(base.BootInterface):
in instance_info for non-Glance image.
"""
_validate_instance_image_info(task)
_validate_driver_info(task)
if not task.driver.storage.should_write_image(task):
return
else:
_validate_instance_image_info(task)
@METRICS.timer('IloVirtualMediaBoot.prepare_ramdisk')
def prepare_ramdisk(self, task, ramdisk_params):
"""Prepares the boot of deploy ramdisk using virtual media.
@ -464,8 +470,12 @@ class IloVirtualMediaBoot(base.BootInterface):
relevant information from the node's instance_info.
It does the following depending on boot_option for deploy:
- If the boot_option requested for this deploy is 'local' or image
is a whole disk image, then it sets the node to boot from disk.
- If the boot mode is 'uefi' and its booting from volume, then it
sets the iSCSI target info and node to boot from 'UefiTarget'
boot device.
- If not 'boot from volume' and the boot_option requested for
this deploy is 'local' or image is a whole disk image, then
it sets the node to boot from disk.
- Otherwise it finds/creates the boot ISO to boot the instance
image, attaches the boot ISO to the bare metal and then sets
the node to boot from CDROM.
@ -473,25 +483,44 @@ class IloVirtualMediaBoot(base.BootInterface):
:param task: a task from TaskManager.
:returns: None
:raises: IloOperationError, if some operation on iLO failed.
:raises: InstanceDeployFailure, if its try to boot iSCSI volume in
'BIOS' boot mode.
"""
ilo_common.cleanup_vmedia_boot(task)
# For iscsi_ilo driver, we boot from disk every time if the image
# deployed is a whole disk image.
node = task.node
iwdi = node.driver_internal_info.get('is_whole_disk_image')
if deploy_utils.get_boot_option(node) == "local" or iwdi:
manager_utils.node_set_boot_device(task, boot_devices.DISK,
persistent=True)
else:
drv_int_info = node.driver_internal_info
root_uuid_or_disk_id = drv_int_info.get('root_uuid_or_disk_id')
if root_uuid_or_disk_id:
self._configure_vmedia_boot(task, root_uuid_or_disk_id)
boot_mode = deploy_utils.get_boot_mode_for_deploy(task.node)
if deploy_utils.is_iscsi_boot(task):
# It will set iSCSI info onto iLO
if 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()
task.driver.management.set_iscsi_boot_target(task)
manager_utils.node_set_boot_device(
task, boot_devices.ISCSIBOOT, persistent=True)
else:
LOG.warning("The UUID for the root partition could not "
"be found for node %s", node.uuid)
msg = 'Virtual media can not boot volume in BIOS boot mode.'
raise exception.InstanceDeployFailure(msg)
else:
# For iscsi_ilo driver, we boot from disk every time if the image
# deployed is a whole disk image.
node = task.node
iwdi = node.driver_internal_info.get('is_whole_disk_image')
if deploy_utils.get_boot_option(node) == "local" or iwdi:
manager_utils.node_set_boot_device(task, boot_devices.DISK,
persistent=True)
else:
drv_int_info = node.driver_internal_info
root_uuid_or_disk_id = drv_int_info.get('root_uuid_or_disk_id')
if root_uuid_or_disk_id:
self._configure_vmedia_boot(task, root_uuid_or_disk_id)
else:
LOG.warning("The UUID for the root partition could not "
"be found for node %s", node.uuid)
# Set boot mode
ilo_common.update_boot_mode(task)
# Need to enable secure boot, if being requested
@ -502,7 +531,9 @@ class IloVirtualMediaBoot(base.BootInterface):
"""Cleans up the boot of instance.
This method cleans up the environment that was setup for booting
the instance. It ejects virtual media
the instance. It ejects virtual media.
In case of UEFI iSCSI booting, it cleans up iSCSI target information
from the node.
:param task: a task from TaskManager.
:returns: None
@ -512,16 +543,23 @@ class IloVirtualMediaBoot(base.BootInterface):
LOG.debug("Cleaning up the instance.")
manager_utils.node_power_action(task, states.POWER_OFF)
disable_secure_boot_if_supported(task)
_clean_up_boot_iso_for_instance(task.node)
driver_internal_info = task.node.driver_internal_info
driver_internal_info.pop('boot_iso_created_in_web_server', None)
driver_internal_info.pop('root_uuid_or_disk_id', None)
task.node.driver_internal_info = driver_internal_info
task.node.save()
ilo_common.cleanup_vmedia_boot(task)
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
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:
_clean_up_boot_iso_for_instance(task.node)
driver_internal_info = task.node.driver_internal_info
driver_internal_info.pop('boot_iso_created_in_web_server', None)
driver_internal_info.pop('root_uuid_or_disk_id', None)
task.node.driver_internal_info = driver_internal_info
task.node.save()
ilo_common.cleanup_vmedia_boot(task)
@METRICS.timer('IloVirtualMediaBoot.clean_up_ramdisk')
def clean_up_ramdisk(self, task):
@ -601,6 +639,8 @@ class IloPXEBoot(pxe.PXEBoot):
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
@ -612,7 +652,22 @@ class IloPXEBoot(pxe.PXEBoot):
# Need to enable secure boot, if being requested
ilo_common.update_secure_boot_mode(task, True)
super(IloPXEBoot, self).prepare_instance(task)
boot_mode = deploy_utils.get_boot_mode_for_deploy(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(IloPXEBoot, self).prepare_instance(task)
@METRICS.timer('IloPXEBoot.clean_up_instance')
def clean_up_instance(self, task):
@ -621,6 +676,8 @@ class IloPXEBoot(pxe.PXEBoot):
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
@ -629,5 +686,17 @@ class IloPXEBoot(pxe.PXEBoot):
manager_utils.node_power_action(task, states.POWER_OFF)
disable_secure_boot_if_supported(task)
driver_internal_info = task.node.driver_internal_info
super(IloPXEBoot, self).clean_up_instance(task)
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(IloPXEBoot, self).clean_up_instance(task)

View File

@ -34,6 +34,7 @@ from ironic.drivers.modules.ilo import common as ilo_common
from ironic.drivers.modules.ilo import firmware_processor
from ironic.drivers.modules import ipmitool
from ironic.drivers import utils as driver_utils
from ironic.objects import volume_target
LOG = logging.getLogger(__name__)
@ -44,7 +45,8 @@ ilo_error = importutils.try_import('proliantutils.exception')
BOOT_DEVICE_MAPPING_TO_ILO = {
boot_devices.PXE: 'NETWORK',
boot_devices.DISK: 'HDD',
boot_devices.CDROM: 'CDROM'
boot_devices.CDROM: 'CDROM',
boot_devices.ISCSIBOOT: 'ISCSI'
}
BOOT_DEVICE_ILO_TO_GENERIC = {
v: k for k, v in BOOT_DEVICE_MAPPING_TO_ILO.items()}
@ -513,3 +515,68 @@ class IloManagement(base.ManagementInterface):
'%(node)s for "update_firmware_sum" clean step. '
'Error: %(error)s',
{'node': node.uuid, 'error': e})
@METRICS.timer('IloManagement.set_iscsi_boot_target')
def set_iscsi_boot_target(self, task):
"""Set iSCSI details of the system in UEFI boot mode.
The initiator is set with the target details like
IQN, LUN, IP, Port etc.
:param task: a task from TaskManager.
:raises: IloCommandNotSupportedInBiosError if system in BIOS boot mode.
:raises: IloError on an error from iLO.
"""
# Getting target info
node = task.node
boot_volume = node.driver_internal_info.get('boot_from_volume')
volume = volume_target.VolumeTarget.get_by_uuid(task.context,
boot_volume)
properties = volume.properties
username = properties.get('auth_username', None)
password = properties.get('auth_password', None)
portal = properties['target_portal']
iqn = properties['target_iqn']
lun = properties['target_lun']
host, port = portal.split(':')
ilo_object = ilo_common.get_ilo_object(task.node)
try:
if username is None:
ilo_object.set_iscsi_info(iqn, lun, host, port)
else:
ilo_object.set_iscsi_info(iqn, lun, host, port, 'CHAP',
username, password)
except ilo_error.IloCommandNotSupportedInBiosError as ilo_exception:
operation = (_("Setting of target IQN %(target_iqn)s for node "
"%(node)s")
% {'target_iqn': iqn, 'node': node.uuid})
raise exception.IloOperationNotSupported(operation=operation,
error=ilo_exception)
except ilo_error.IloError as ilo_exception:
operation = (_("Setting of target IQN %(target_iqn)s for node "
"%(node)s")
% {'target_iqn': iqn, 'node': node.uuid})
raise exception.IloOperationError(operation=operation,
error=ilo_exception)
@METRICS.timer('IloManagement.clear_iscsi_boot_target')
def clear_iscsi_boot_target(self, task):
"""Unset iSCSI details of the system in UEFI boot mode.
:param task: a task from TaskManager.
:raises: IloCommandNotSupportedInBiosError if system in BIOS boot mode.
:raises: IloError on an error from iLO.
"""
ilo_object = ilo_common.get_ilo_object(task.node)
try:
ilo_object.unset_iscsi_info()
except ilo_error.IloCommandNotSupportedInBiosError as ilo_exception:
operation = (_("Unsetting of iSCSI target for node %(node)s")
% {'node': task.node.uuid})
raise exception.IloOperationNotSupported(operation=operation,
error=ilo_exception)
except ilo_error.IloError as ilo_exception:
operation = (_("Unsetting of iSCSI target for node %(node)s")
% {'node': task.node.uuid})
raise exception.IloOperationError(operation=operation,
error=ilo_exception)

View File

@ -34,7 +34,9 @@ from ironic.conductor import utils as manager_utils
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 pxe
from ironic.drivers.modules.storage import noop as noop_storage
from ironic.drivers import utils as driver_utils
from ironic.tests.unit.conductor import mgr_utils
from ironic.tests.unit.db import base as db_base
@ -674,12 +676,14 @@ class IloVirtualMediaBootTestCase(db_base.DbTestCase):
self.node = obj_utils.create_test_node(
self.context, driver='iscsi_ilo', driver_info=INFO_DICT)
@mock.patch.object(noop_storage.NoopStorage, 'should_write_image',
autospec=True)
@mock.patch.object(ilo_boot, '_validate_driver_info',
spec_set=True, autospec=True)
@mock.patch.object(ilo_boot, '_validate_instance_image_info',
spec_set=True, autospec=True)
def test_validate(self, mock_val_instance_image_info,
mock_val_driver_info):
mock_val_driver_info, storage_mock):
instance_info = self.node.instance_info
instance_info['ilo_boot_iso'] = 'deploy-iso'
instance_info['image_source'] = '6b2f0c0c-79e8-4db6-842e-43c9764204af'
@ -689,10 +693,24 @@ class IloVirtualMediaBootTestCase(db_base.DbTestCase):
shared=False) as task:
task.node.driver_info['ilo_deploy_iso'] = 'deploy-iso'
storage_mock.return_value = True
task.driver.boot.validate(task)
mock_val_instance_image_info.assert_called_once_with(task)
mock_val_driver_info.assert_called_once_with(task)
@mock.patch.object(noop_storage.NoopStorage, 'should_write_image',
autospec=True)
@mock.patch.object(ilo_boot, '_validate_driver_info',
spec_set=True, autospec=True)
def test_validate_boot_from_volume(self, mock_val_driver_info,
storage_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.node.driver_info['ilo_deploy_iso'] = 'deploy-iso'
storage_mock.return_value = False
task.driver.boot.validate(task)
mock_val_driver_info.assert_called_once_with(task)
@mock.patch.object(ilo_boot, 'prepare_node_for_deploy',
spec_set=True, autospec=True)
@mock.patch.object(manager_utils, 'node_power_action',
@ -834,6 +852,8 @@ class IloVirtualMediaBootTestCase(db_base.DbTestCase):
self.assertFalse(setup_vmedia_mock.called)
self.assertFalse(set_boot_device_mock.called)
@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,
@ -844,7 +864,8 @@ class IloVirtualMediaBootTestCase(db_base.DbTestCase):
spec_set=True, autospec=True)
def test_clean_up_instance(self, cleanup_iso_mock,
cleanup_vmedia_mock, node_power_mock,
update_secure_boot_mode_mock):
update_secure_boot_mode_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
@ -853,6 +874,62 @@ class IloVirtualMediaBootTestCase(db_base.DbTestCase):
"12312642-09d3-467f-8e09-12385826a123")
task.node.driver_internal_info = driver_internal_info
task.node.save()
is_iscsi_boot_mock.return_value = False
task.driver.boot.clean_up_instance(task)
cleanup_iso_mock.assert_called_once_with(task.node)
cleanup_vmedia_mock.assert_called_once_with(task)
driver_internal_info = task.node.driver_internal_info
self.assertNotIn('boot_iso_created_in_web_server',
driver_internal_info)
self.assertNotIn('root_uuid_or_disk_id', driver_internal_info)
node_power_mock.assert_called_once_with(task,
states.POWER_OFF)
update_secure_boot_mode_mock.assert_called_once_with(task, False)
@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)
node_power_mock.assert_called_once_with(task,
states.POWER_OFF)
clear_iscsi_boot_target_mock.assert_called_once_with(mock.ANY,
task)
update_secure_boot_mode_mock.assert_called_once_with(task, False)
self.assertIsNone(self.node.driver_internal_info.get(
'ilo_uefi_iscsi_boot'))
@mock.patch.object(ilo_common, 'cleanup_vmedia_boot', spec_set=True,
autospec=True)
@mock.patch.object(ilo_boot, '_clean_up_boot_iso_for_instance',
spec_set=True, autospec=True)
@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)
def test_clean_up_instance_boot_from_volume_bios(
self, node_power_mock, update_secure_boot_mode_mock,
is_iscsi_boot_mock, cleanup_iso_mock, cleanup_vmedia_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
is_iscsi_boot_mock.return_value = True
task.driver.boot.clean_up_instance(task)
cleanup_iso_mock.assert_called_once_with(task.node)
cleanup_vmedia_mock.assert_called_once_with(task)
@ -872,6 +949,8 @@ class IloVirtualMediaBootTestCase(db_base.DbTestCase):
task.driver.boot.clean_up_ramdisk(task)
cleanup_vmedia_mock.assert_called_once_with(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(ilo_common, 'update_boot_mode', spec_set=True,
@ -882,9 +961,11 @@ class IloVirtualMediaBootTestCase(db_base.DbTestCase):
autospec=True)
def _test_prepare_instance_whole_disk_image(
self, cleanup_vmedia_boot_mock, set_boot_device_mock,
update_boot_mode_mock, update_secure_boot_mode_mock):
update_boot_mode_mock, update_secure_boot_mode_mock,
is_iscsi_boot_mock):
self.node.driver_internal_info = {'is_whole_disk_image': True}
self.node.save()
is_iscsi_boot_mock.return_value = False
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.boot.prepare_instance(task)
@ -904,6 +985,8 @@ class IloVirtualMediaBootTestCase(db_base.DbTestCase):
def test_prepare_instance_whole_disk_image(self):
self._test_prepare_instance_whole_disk_image()
@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(ilo_common, 'update_boot_mode', spec_set=True,
@ -915,20 +998,71 @@ class IloVirtualMediaBootTestCase(db_base.DbTestCase):
autospec=True)
def test_prepare_instance_partition_image(
self, cleanup_vmedia_boot_mock, configure_vmedia_mock,
update_boot_mode_mock, update_secure_boot_mode_mock):
update_boot_mode_mock, update_secure_boot_mode_mock,
is_iscsi_boot_mock):
self.node.driver_internal_info = {'root_uuid_or_disk_id': (
"12312642-09d3-467f-8e09-12385826a123")}
self.node.save()
is_iscsi_boot_mock.return_value = False
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.boot.prepare_instance(task)
cleanup_vmedia_boot_mock.assert_called_once_with(task)
configure_vmedia_mock.assert_called_once_with(
mock.ANY, task, "12312642-09d3-467f-8e09-12385826a123")
update_boot_mode_mock.assert_called_once_with(task)
update_secure_boot_mode_mock.assert_called_once_with(task, True)
@mock.patch.object(ilo_common, 'cleanup_vmedia_boot', spec_set=True,
autospec=True)
@mock.patch.object(deploy_utils, 'is_iscsi_boot',
spec_set=True, autospec=True)
@mock.patch.object(deploy_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, cleanup_vmedia_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)
cleanup_vmedia_boot_mock.assert_called_once_with(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)
@mock.patch.object(ilo_common, 'cleanup_vmedia_boot', spec_set=True,
autospec=True)
@mock.patch.object(deploy_utils, 'is_iscsi_boot',
spec_set=True, autospec=True)
@mock.patch.object(deploy_utils, 'get_boot_mode_for_deploy',
spec_set=True, autospec=True)
def test_prepare_instance_boot_from_volume_bios(
self, get_boot_mode_mock,
is_iscsi_boot_mock, cleanup_vmedia_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 = 'bios'
self.assertRaisesRegex(exception.InstanceDeployFailure,
"Virtual media can not boot volume "
"in BIOS boot mode.",
task.driver.boot.prepare_instance, task)
cleanup_vmedia_boot_mock.assert_called_once_with(task)
class IloPXEBootTestCase(db_base.DbTestCase):
@ -972,6 +1106,8 @@ class IloPXEBootTestCase(db_base.DbTestCase):
pxe_prepare_instance_mock.assert_called_once_with(mock.ANY,
task, None)
@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,
@ -979,15 +1115,39 @@ class IloPXEBootTestCase(db_base.DbTestCase):
@mock.patch.object(pxe.PXEBoot, '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):
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(pxe.PXEBoot, '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(deploy_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,
@ -996,11 +1156,95 @@ class IloPXEBootTestCase(db_base.DbTestCase):
autospec=True)
def test_prepare_instance(self, pxe_prepare_instance_mock,
update_boot_mode_mock,
update_secure_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)
@mock.patch.object(deploy_utils, 'is_iscsi_boot',
spec_set=True, autospec=True)
@mock.patch.object(deploy_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(pxe.PXEBoot, '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)
@mock.patch.object(deploy_utils, 'is_iscsi_boot',
spec_set=True, autospec=True)
@mock.patch.object(deploy_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.assertIsNone(self.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(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(self.node.driver_internal_info.get(
'ilo_uefi_iscsi_boot'))

View File

@ -16,6 +16,7 @@
import mock
from oslo_utils import importutils
from oslo_utils import uuidutils
from ironic.common import boot_devices
from ironic.common import exception
@ -63,7 +64,7 @@ class IloManagementTestCase(db_base.DbTestCase):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
expected = [boot_devices.PXE, boot_devices.DISK,
boot_devices.CDROM]
boot_devices.CDROM, boot_devices.ISCSIBOOT]
self.assertEqual(
sorted(expected),
sorted(task.driver.management.
@ -709,3 +710,132 @@ class IloManagementTestCase(db_base.DbTestCase):
task.driver.management._update_firmware_sum_final(
task, command)
self.assertTrue(log_mock.exception.called)
@mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
autospec=True)
def test_set_iscsi_boot_target_with_auth(self, get_ilo_object_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
vol_id = uuidutils.generate_uuid()
obj_utils.create_test_volume_target(
self.context, node_id=self.node.id, volume_type='iscsi',
boot_index=0, volume_id='1234', uuid=vol_id,
properties={'target_lun': 0,
'target_portal': 'fake_host:3260',
'target_iqn': 'fake_iqn',
'auth_username': 'fake_username',
'auth_password': 'fake_password'})
driver_internal_info = task.node.driver_internal_info
driver_internal_info['boot_from_volume'] = vol_id
task.node.driver_internal_info = driver_internal_info
task.node.save()
ilo_object_mock = get_ilo_object_mock.return_value
task.driver.management.set_iscsi_boot_target(task)
ilo_object_mock.set_iscsi_info.assert_called_once_with(
'fake_iqn', 0, 'fake_host', '3260',
'CHAP', 'fake_username', 'fake_password')
@mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
autospec=True)
def test_set_iscsi_boot_target_without_auth(self, get_ilo_object_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
vol_id = uuidutils.generate_uuid()
obj_utils.create_test_volume_target(
self.context, node_id=self.node.id, volume_type='iscsi',
boot_index=0, volume_id='1234', uuid=vol_id,
properties={'target_lun': 0,
'target_portal': 'fake_host:3260',
'target_iqn': 'fake_iqn'})
driver_internal_info = task.node.driver_internal_info
driver_internal_info['boot_from_volume'] = vol_id
task.node.driver_internal_info = driver_internal_info
task.node.save()
ilo_object_mock = get_ilo_object_mock.return_value
task.driver.management.set_iscsi_boot_target(task)
ilo_object_mock.set_iscsi_info.assert_called_once_with(
'fake_iqn', 0, 'fake_host', '3260')
@mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
autospec=True)
def test_set_iscsi_boot_target_failed(self, get_ilo_object_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
vol_id = uuidutils.generate_uuid()
obj_utils.create_test_volume_target(
self.context, node_id=self.node.id, volume_type='iscsi',
boot_index=0, volume_id='1234', uuid=vol_id,
properties={'target_lun': 0,
'target_portal': 'fake_host:3260',
'target_iqn': 'fake_iqn',
'auth_username': 'fake_username',
'auth_password': 'fake_password'})
driver_internal_info = task.node.driver_internal_info
driver_internal_info['boot_from_volume'] = vol_id
task.node.driver_internal_info = driver_internal_info
task.node.save()
ilo_object_mock = get_ilo_object_mock.return_value
ilo_object_mock.set_iscsi_info.side_effect = (
ilo_error.IloError)
self.assertRaises(exception.IloOperationError,
task.driver.management.set_iscsi_boot_target,
task)
@mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
autospec=True)
def test_set_iscsi_boot_target_in_bios(self, get_ilo_object_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
vol_id = uuidutils.generate_uuid()
obj_utils.create_test_volume_target(
self.context, node_id=self.node.id, volume_type='iscsi',
boot_index=0, volume_id='1234', uuid=vol_id,
properties={'target_lun': 0,
'target_portal': 'fake_host:3260',
'target_iqn': 'fake_iqn',
'auth_username': 'fake_username',
'auth_password': 'fake_password'})
driver_internal_info = task.node.driver_internal_info
driver_internal_info['boot_from_volume'] = vol_id
task.node.driver_internal_info = driver_internal_info
task.node.save()
ilo_object_mock = get_ilo_object_mock.return_value
ilo_object_mock.set_iscsi_info.side_effect = (
ilo_error.IloCommandNotSupportedInBiosError)
self.assertRaises(exception.IloOperationNotSupported,
task.driver.management.set_iscsi_boot_target,
task)
@mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
autospec=True)
def test_clear_iscsi_boot_target(self, get_ilo_object_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
ilo_object_mock = get_ilo_object_mock.return_value
task.driver.management.clear_iscsi_boot_target(task)
ilo_object_mock.unset_iscsi_info.assert_called_once()
@mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
autospec=True)
def test_clear_iscsi_boot_target_failed(self, get_ilo_object_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
ilo_object_mock = get_ilo_object_mock.return_value
ilo_object_mock.unset_iscsi_info.side_effect = (
ilo_error.IloError)
self.assertRaises(exception.IloOperationError,
task.driver.management.clear_iscsi_boot_target,
task)
@mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
autospec=True)
def test_clear_iscsi_boot_target_in_bios(self, get_ilo_object_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
ilo_object_mock = get_ilo_object_mock.return_value
ilo_object_mock.unset_iscsi_info.side_effect = (
ilo_error.IloCommandNotSupportedInBiosError)
self.assertRaises(exception.IloOperationNotSupported,
task.driver.management.clear_iscsi_boot_target,
task)

View File

@ -61,6 +61,8 @@ if not proliantutils:
proliantutils.exception.IloError = type('IloError', (Exception,), {})
command_exception = type('IloCommandNotSupportedError', (Exception,), {})
proliantutils.exception.IloCommandNotSupportedError = command_exception
proliantutils.exception.IloCommandNotSupportedInBiosError = type(
'IloCommandNotSupportedInBiosError', (Exception,), {})
proliantutils.exception.InvalidInputError = type(
'InvalidInputError', (Exception,), {})
proliantutils.exception.ImageExtractionFailed = type(

View File

@ -0,0 +1,9 @@
---
features:
- Enhanced boot interface 'ilo-pxe' and 'ilo-virtual-media' to support
firmware based booting from iSCSI volume.
upgrade:
- The ``update_persistent_boot`` and ``[un]set_iscsi_info`` interfaces
of 'proliantutils' library has been enhanced to support booting from
an iSCSI volume. To leverage this feature, the 'proliantutils' library
needs to be upgraded to version '2.5.0'.