Iso booting via redfish virtual media
Adds support to signal a pass-through request to the redfish-virtual-media boot interface so a user can supply a boot ISO to boot the machine. Tested on an HPE Edgeline e910 series machine using the ``redfish-virtual-media`` boot interface. Story: 2007633 Task: 39823 Change-Id: Ie74472969c75994794dc0ca19bbe7cfd395855c9
This commit is contained in:
parent
bf65acf6ba
commit
bd0033611d
@ -185,6 +185,29 @@ property can be used to pass user-specified kernel command line parameters.
|
||||
For ramdisk kernel, ``[instance_info]/kernel_append_params`` property serves
|
||||
the same purpose.
|
||||
|
||||
Virtual Media Ramdisk
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``ramdisk`` deploy interface can be used in concert with the the
|
||||
``redfish-virtual-media`` boot interface to facilitate the boot of a remote
|
||||
node utilizing pre-supplied virtual media.
|
||||
|
||||
Instead of supplying an ``[instance_info]/image_source`` parameter, a
|
||||
``[instance_info]/boot_iso`` parameter can be supplied. The image will
|
||||
be downloaded by the conductor, and the instance will be booted using
|
||||
the supplied ISO image. In accordance with the ``ramdisk`` deployment
|
||||
interface behavior, once booted the machine will have a ``provision_state``
|
||||
of ``ACTIVE``.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
openstack baremetal node set \
|
||||
--instance_info boot_iso=http://url/to.iso node-0
|
||||
|
||||
This initial interface does not support bootloader configuration
|
||||
parameter injection, as such the ``[instance_info]/kernel_append_params``
|
||||
setting is ignored.
|
||||
|
||||
.. _Redfish: http://redfish.dmtf.org/
|
||||
.. _Sushy: https://opendev.org/openstack/sushy
|
||||
.. _TLS: https://en.wikipedia.org/wiki/Transport_Layer_Security
|
||||
|
@ -526,7 +526,7 @@ def get_temp_url_for_glance_image(context, image_uuid):
|
||||
def create_boot_iso(context, output_filename, kernel_href,
|
||||
ramdisk_href, deploy_iso_href=None, esp_image_href=None,
|
||||
root_uuid=None, kernel_params=None, boot_mode=None,
|
||||
configdrive_href=None):
|
||||
configdrive_href=None, base_iso=None):
|
||||
"""Creates a bootable ISO image for a node.
|
||||
|
||||
Given the hrefs for kernel, ramdisk, root partition's UUID and
|
||||
@ -553,14 +553,26 @@ def create_boot_iso(context, output_filename, kernel_href,
|
||||
:param configdrive_href: URL to ISO9660 or FAT-formatted OpenStack config
|
||||
drive image. This image will be embedded into the built ISO image.
|
||||
Optional.
|
||||
:param base_iso: URL or glance UUID of a to be used as an override of
|
||||
what should be retrieved for to use, instead of building an ISO
|
||||
bootable ramdisk.
|
||||
:raises: ImageCreationFailed, if creating boot ISO failed.
|
||||
"""
|
||||
with utils.tempdir() as tmpdir:
|
||||
kernel_path = os.path.join(tmpdir, kernel_href.split('/')[-1])
|
||||
ramdisk_path = os.path.join(tmpdir, ramdisk_href.split('/')[-1])
|
||||
|
||||
fetch(context, kernel_href, kernel_path)
|
||||
fetch(context, ramdisk_href, ramdisk_path)
|
||||
if base_iso:
|
||||
# NOTE(TheJulia): Eventually we want to use the creation method
|
||||
# to perform the massaging of the image, because oddly enough
|
||||
# we need to do all the same basic things, just a little
|
||||
# differently.
|
||||
fetch(context, base_iso, output_filename)
|
||||
# Temporary, return to the caller until we support the combined
|
||||
# operation.
|
||||
return
|
||||
else:
|
||||
kernel_path = os.path.join(tmpdir, kernel_href.split('/')[-1])
|
||||
ramdisk_path = os.path.join(tmpdir, ramdisk_href.split('/')[-1])
|
||||
fetch(context, kernel_href, kernel_path)
|
||||
fetch(context, ramdisk_href, ramdisk_path)
|
||||
|
||||
if configdrive_href:
|
||||
configdrive_path = os.path.join(
|
||||
@ -592,7 +604,11 @@ def create_boot_iso(context, output_filename, kernel_href,
|
||||
|
||||
elif CONF.esp_image:
|
||||
esp_image_path = CONF.esp_image
|
||||
|
||||
# TODO(TheJulia): we should opportunisticly try to make bios
|
||||
# bootable and UEFI. In other words, collapse a lot of this
|
||||
# path since they are not mutually exclusive.
|
||||
# UEFI boot mode, but Network iPXE -> ISO means bios bootable
|
||||
# contents are still required.
|
||||
create_esp_image_for_uefi(
|
||||
output_filename, kernel_path, ramdisk_path,
|
||||
deploy_iso=deploy_iso_path, esp_image=esp_image_path,
|
||||
|
@ -511,6 +511,11 @@ def validate_image_properties(ctx, deploy_info, properties):
|
||||
the mentioned properties.
|
||||
"""
|
||||
image_href = deploy_info['image_source']
|
||||
boot_iso = deploy_info.get('boot_iso')
|
||||
if image_href and boot_iso:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"An 'image_source' and 'boot_iso' parameter may not be "
|
||||
"specified at the same time."))
|
||||
try:
|
||||
img_service = image_service.get_image_service(image_href, context=ctx)
|
||||
image_props = img_service.show(image_href)['properties']
|
||||
@ -697,11 +702,21 @@ def get_image_instance_info(node):
|
||||
instance_info. Also raises same exception if kernel/ramdisk is
|
||||
missing in instance_info for non-glance images.
|
||||
"""
|
||||
# TODO(TheJulia): We seem to have a lack of direct unit testing of this
|
||||
# method, but that is likely okay. If memory serves we test this at
|
||||
# a few different levels. That being said, it would be good for some
|
||||
# more explicit unit testing to exist.
|
||||
info = {}
|
||||
info['image_source'] = node.instance_info.get('image_source')
|
||||
|
||||
is_whole_disk_image = node.driver_internal_info.get('is_whole_disk_image')
|
||||
if not is_whole_disk_image:
|
||||
boot_iso = node.instance_info.get('boot_iso')
|
||||
|
||||
if not boot_iso:
|
||||
info['image_source'] = node.instance_info.get('image_source')
|
||||
else:
|
||||
info['boot_iso'] = boot_iso
|
||||
|
||||
if not is_whole_disk_image and not boot_iso:
|
||||
if not service_utils.is_glance_image(info['image_source']):
|
||||
info['kernel'] = node.instance_info.get('kernel')
|
||||
info['ramdisk'] = node.instance_info.get('ramdisk')
|
||||
|
@ -458,7 +458,7 @@ def _parse_deploy_info(node):
|
||||
|
||||
def _prepare_iso_image(task, kernel_href, ramdisk_href,
|
||||
bootloader_href=None, configdrive=None,
|
||||
root_uuid=None, params=None):
|
||||
root_uuid=None, params=None, base_iso=None):
|
||||
"""Prepare an ISO to boot the node.
|
||||
|
||||
Build bootable ISO out of `kernel_href` and `ramdisk_href` (and
|
||||
@ -486,23 +486,28 @@ def _prepare_iso_image(task, kernel_href, ramdisk_href,
|
||||
value.
|
||||
:raises: ImageCreationFailed, if creating ISO image failed.
|
||||
"""
|
||||
if not kernel_href or not ramdisk_href:
|
||||
if (not kernel_href or not ramdisk_href) and not base_iso:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Unable to find kernel or ramdisk for "
|
||||
"building ISO for %(node)s") %
|
||||
"Unable to find kernel, ramdisk for "
|
||||
"building ISO, or explicit ISO for %(node)s") %
|
||||
{'node': task.node.uuid})
|
||||
|
||||
i_info = task.node.instance_info
|
||||
|
||||
# NOTE(TheJulia): Until we support modifying a base iso, most of
|
||||
# this logic actually does nothing in the end. But it should!
|
||||
if deploy_utils.get_boot_option(task.node) == "ramdisk":
|
||||
kernel_params = "root=/dev/ram0 text "
|
||||
kernel_params += i_info.get("ramdisk_kernel_arguments", "")
|
||||
if not base_iso:
|
||||
kernel_params = "root=/dev/ram0 text "
|
||||
kernel_params += i_info.get("ramdisk_kernel_arguments", "")
|
||||
else:
|
||||
kernel_params = None
|
||||
|
||||
else:
|
||||
kernel_params = i_info.get(
|
||||
'kernel_append_params', CONF.redfish.kernel_append_params)
|
||||
|
||||
if params:
|
||||
if params and not base_iso:
|
||||
kernel_params = ' '.join(
|
||||
(kernel_params, ' '.join(
|
||||
'%s=%s' % kv for kv in params.items())))
|
||||
@ -527,7 +532,11 @@ def _prepare_iso_image(task, kernel_href, ramdisk_href,
|
||||
|
||||
configdrive_href = configdrive
|
||||
|
||||
if configdrive:
|
||||
# FIXME(TheJulia): This is treated as conditional with
|
||||
# a base_iso as the intent, eventually, is to support
|
||||
# injection into the supplied image.
|
||||
|
||||
if configdrive and not base_iso:
|
||||
parsed_url = urlparse.urlparse(configdrive)
|
||||
if not parsed_url.scheme:
|
||||
cfgdrv_blob = base64.decode_as_bytes(configdrive)
|
||||
@ -549,7 +558,8 @@ def _prepare_iso_image(task, kernel_href, ramdisk_href,
|
||||
configdrive_href=configdrive_href,
|
||||
root_uuid=root_uuid,
|
||||
kernel_params=kernel_params,
|
||||
boot_mode=boot_mode)
|
||||
boot_mode=boot_mode,
|
||||
base_iso=base_iso)
|
||||
|
||||
iso_object_name = _get_iso_image_name(task.node)
|
||||
|
||||
@ -597,6 +607,9 @@ def _prepare_deploy_iso(task, params, mode):
|
||||
ramdisk_href = d_info.get('%s_ramdisk' % mode)
|
||||
bootloader_href = d_info.get('bootloader')
|
||||
|
||||
# TODO(TheJulia): At some point we should support something like
|
||||
# boot_iso for the deploy interface, perhaps when we support config
|
||||
# injection.
|
||||
prepare_iso_image = functools.partial(
|
||||
_prepare_iso_image, task, kernel_href, ramdisk_href,
|
||||
bootloader_href=bootloader_href, params=params)
|
||||
@ -656,8 +669,9 @@ def _prepare_boot_iso(task, root_uuid=None):
|
||||
|
||||
kernel_href = node.instance_info.get('kernel')
|
||||
ramdisk_href = node.instance_info.get('ramdisk')
|
||||
base_iso = node.instance_info.get('boot_iso')
|
||||
|
||||
if not kernel_href or not ramdisk_href:
|
||||
if (not kernel_href or not ramdisk_href) and not base_iso:
|
||||
|
||||
image_href = d_info['image_source']
|
||||
|
||||
@ -671,17 +685,17 @@ def _prepare_boot_iso(task, root_uuid=None):
|
||||
if not ramdisk_href:
|
||||
ramdisk_href = image_properties.get('ramdisk_id')
|
||||
|
||||
if not kernel_href or not ramdisk_href:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Unable to find kernel or ramdisk for "
|
||||
"to generate boot ISO for %(node)s") %
|
||||
{'node': task.node.uuid})
|
||||
if (not kernel_href or not ramdisk_href):
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Unable to find kernel or ramdisk for "
|
||||
"to generate boot ISO for %(node)s") %
|
||||
{'node': task.node.uuid})
|
||||
|
||||
bootloader_href = d_info.get('bootloader')
|
||||
|
||||
return _prepare_iso_image(
|
||||
task, kernel_href, ramdisk_href, bootloader_href,
|
||||
root_uuid=root_uuid)
|
||||
root_uuid=root_uuid, base_iso=base_iso)
|
||||
|
||||
|
||||
class RedfishVirtualMediaBoot(base.BootInterface):
|
||||
@ -767,7 +781,8 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
||||
|
||||
if node.driver_internal_info.get('is_whole_disk_image'):
|
||||
props = []
|
||||
|
||||
elif d_info.get('boot_iso'):
|
||||
props = ['boot_iso']
|
||||
elif service_utils.is_glance_image(d_info['image_source']):
|
||||
props = ['kernel_id', 'ramdisk_id']
|
||||
|
||||
|
@ -911,6 +911,27 @@ class FsImageTestCase(base.TestCase):
|
||||
'output_file', 'tmpdir/kernel-uuid', 'tmpdir/ramdisk-uuid',
|
||||
configdrive='tmpdir/configdrive', kernel_params=params)
|
||||
|
||||
@mock.patch.object(images, 'create_isolinux_image_for_bios', autospec=True)
|
||||
@mock.patch.object(images, 'fetch', autospec=True)
|
||||
@mock.patch.object(utils, 'tempdir', autospec=True)
|
||||
def test_create_boot_iso_for_existing_iso(self, tempdir_mock,
|
||||
fetch_images_mock,
|
||||
create_isolinux_mock):
|
||||
mock_file_handle = mock.MagicMock(spec=io.BytesIO)
|
||||
mock_file_handle.__enter__.return_value = 'tmpdir'
|
||||
tempdir_mock.return_value = mock_file_handle
|
||||
base_iso = 'http://fake.local:1234/fake.iso'
|
||||
images.create_boot_iso('ctx', 'output_file', 'kernel-uuid',
|
||||
'ramdisk-uuid', 'deploy_iso-uuid',
|
||||
'efiboot-uuid', None,
|
||||
None, None, 'http://configdrive',
|
||||
base_iso=base_iso)
|
||||
|
||||
fetch_images_mock.assert_any_call(
|
||||
'ctx', base_iso, 'output_file')
|
||||
|
||||
create_isolinux_mock.assert_not_called()
|
||||
|
||||
@mock.patch.object(image_service, 'get_image_service', autospec=True)
|
||||
def test_get_glance_image_properties_no_such_prop(self,
|
||||
image_service_mock):
|
||||
|
@ -340,7 +340,8 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
|
||||
url = redfish_boot._prepare_iso_image(
|
||||
task, 'http://kernel/img', 'http://ramdisk/img',
|
||||
'http://bootloader/img', root_uuid=task.node.uuid)
|
||||
'http://bootloader/img', root_uuid=task.node.uuid,
|
||||
base_iso=None)
|
||||
|
||||
object_name = 'boot-%s' % task.node.uuid
|
||||
|
||||
@ -352,7 +353,8 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
boot_mode='uefi', esp_image_href='http://bootloader/img',
|
||||
configdrive_href=mock.ANY,
|
||||
kernel_params='nofb nomodeset vga=normal',
|
||||
root_uuid='1be26c0b-03f2-4d2e-ae87-c02d7f33c123')
|
||||
root_uuid='1be26c0b-03f2-4d2e-ae87-c02d7f33c123',
|
||||
base_iso=None)
|
||||
|
||||
self.assertEqual(expected_url, url)
|
||||
|
||||
@ -381,7 +383,8 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
boot_mode=None, esp_image_href=None,
|
||||
configdrive_href=mock.ANY,
|
||||
kernel_params='nofb nomodeset vga=normal',
|
||||
root_uuid='1be26c0b-03f2-4d2e-ae87-c02d7f33c123')
|
||||
root_uuid='1be26c0b-03f2-4d2e-ae87-c02d7f33c123',
|
||||
base_iso=None)
|
||||
|
||||
self.assertEqual(expected_url, url)
|
||||
|
||||
@ -397,14 +400,39 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
|
||||
redfish_boot._prepare_iso_image(
|
||||
task, 'http://kernel/img', 'http://ramdisk/img',
|
||||
bootloader_href=None, root_uuid=task.node.uuid)
|
||||
bootloader_href=None, root_uuid=task.node.uuid,
|
||||
base_iso=None)
|
||||
|
||||
mock_create_boot_iso.assert_called_once_with(
|
||||
mock.ANY, mock.ANY, 'http://kernel/img', 'http://ramdisk/img',
|
||||
boot_mode=None, esp_image_href=None,
|
||||
configdrive_href=mock.ANY,
|
||||
kernel_params=kernel_params,
|
||||
root_uuid='1be26c0b-03f2-4d2e-ae87-c02d7f33c123')
|
||||
root_uuid='1be26c0b-03f2-4d2e-ae87-c02d7f33c123',
|
||||
base_iso=None)
|
||||
|
||||
@mock.patch.object(redfish_boot, '_publish_image', autospec=True)
|
||||
@mock.patch.object(images, 'create_boot_iso', autospec=True)
|
||||
def test__prepare_iso_image_boot_iso(
|
||||
self, mock_create_boot_iso, mock__publish_image):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
|
||||
task.node.instance_info = {'boot_iso': 'http://host/boot.iso',
|
||||
'capabilities': {
|
||||
'boot_option': 'ramdisk'}}
|
||||
|
||||
redfish_boot._prepare_iso_image(
|
||||
task, None, None, root_uuid=None,
|
||||
base_iso='http://host/boot.iso')
|
||||
|
||||
mock_create_boot_iso.assert_called_once_with(
|
||||
mock.ANY, mock.ANY, None, None,
|
||||
boot_mode=None, esp_image_href=None,
|
||||
configdrive_href=None,
|
||||
kernel_params=None,
|
||||
root_uuid=None,
|
||||
base_iso='http://host/boot.iso')
|
||||
|
||||
@mock.patch.object(redfish_boot, '_prepare_iso_image', autospec=True)
|
||||
def test__prepare_deploy_iso(self, mock__prepare_iso_image):
|
||||
@ -474,7 +502,30 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
|
||||
mock__prepare_iso_image.assert_called_once_with(
|
||||
mock.ANY, 'http://kernel/img', 'http://ramdisk/img',
|
||||
'bootloader', root_uuid=task.node.uuid)
|
||||
'bootloader', root_uuid=task.node.uuid, base_iso=None)
|
||||
|
||||
@mock.patch.object(redfish_boot, '_prepare_iso_image', autospec=True)
|
||||
@mock.patch.object(images, 'create_boot_iso', autospec=True)
|
||||
def test__prepare_boot_iso_user_supplied(self, mock_create_boot_iso,
|
||||
mock__prepare_iso_image):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.driver_info.update(
|
||||
{'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'}
|
||||
)
|
||||
|
||||
task.node.instance_info.update(
|
||||
{'boot_iso': 'http://boot/iso'})
|
||||
|
||||
redfish_boot._prepare_boot_iso(
|
||||
task, root_uuid=task.node.uuid)
|
||||
|
||||
mock__prepare_iso_image.assert_called_once_with(
|
||||
mock.ANY, None, None,
|
||||
'bootloader', root_uuid=task.node.uuid,
|
||||
base_iso='http://boot/iso')
|
||||
|
||||
@mock.patch.object(redfish_utils, 'parse_driver_info', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'validate_image_properties',
|
||||
@ -534,6 +585,64 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
mock_validate_image_properties.assert_called_once_with(
|
||||
mock.ANY, mock.ANY, mock.ANY)
|
||||
|
||||
@mock.patch.object(redfish_utils, 'parse_driver_info', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'validate_image_properties',
|
||||
autospec=True)
|
||||
@mock.patch.object(boot_mode_utils, 'get_boot_mode_for_deploy',
|
||||
autospec=True)
|
||||
def test_validate_bios_boot_iso(self, mock_get_boot_mode,
|
||||
mock_validate_image_properties,
|
||||
mock_parse_driver_info):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.instance_info.update(
|
||||
{'boot_iso': 'http://localhost/file.iso'}
|
||||
)
|
||||
|
||||
task.node.driver_info.update(
|
||||
{'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'}
|
||||
)
|
||||
# NOTE(TheJulia): Boot mode doesn't matter for this
|
||||
# test scenario.
|
||||
mock_get_boot_mode.return_value = 'bios'
|
||||
|
||||
task.driver.boot.validate(task)
|
||||
|
||||
mock_validate_image_properties.assert_called_once_with(
|
||||
mock.ANY, mock.ANY, mock.ANY)
|
||||
|
||||
@mock.patch.object(redfish_utils, 'parse_driver_info', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'validate_image_properties',
|
||||
autospec=True)
|
||||
@mock.patch.object(boot_mode_utils, 'get_boot_mode_for_deploy',
|
||||
autospec=True)
|
||||
def test_validate_bios_boot_iso_conflicting_image_source(
|
||||
self, mock_get_boot_mode,
|
||||
mock_validate_image_properties,
|
||||
mock_parse_driver_info):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.instance_info.update(
|
||||
{'boot_iso': 'http://localhost/file.iso',
|
||||
'image_source': 'http://localhost/file.img'}
|
||||
)
|
||||
|
||||
task.node.driver_info.update(
|
||||
{'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'}
|
||||
)
|
||||
# NOTE(TheJulia): Boot mode doesn't matter for this
|
||||
# test scenario.
|
||||
mock_get_boot_mode.return_value = 'bios'
|
||||
|
||||
task.driver.boot.validate(task)
|
||||
|
||||
mock_validate_image_properties.assert_called_once_with(
|
||||
mock.ANY, mock.ANY, mock.ANY)
|
||||
|
||||
@mock.patch.object(redfish_utils, 'parse_driver_info', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'validate_image_properties',
|
||||
autospec=True)
|
||||
@ -841,6 +950,85 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
|
||||
mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task)
|
||||
|
||||
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
|
||||
'clean_up_instance', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_prepare_boot_iso', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_insert_vmedia', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_parse_driver_info', autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'manager_utils', autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
||||
def test_prepare_instance_ramdisk_boot_iso(
|
||||
self, mock_boot_mode_utils, mock_deploy_utils, mock_manager_utils,
|
||||
mock__parse_driver_info, mock__insert_vmedia, mock__eject_vmedia,
|
||||
mock__prepare_boot_iso, mock_clean_up_instance):
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.provision_state = states.DEPLOYING
|
||||
task.node.driver_internal_info[
|
||||
'root_uuid_or_disk_id'] = self.node.uuid
|
||||
task.node.instance_info = {'boot_iso': 'http://host/boot.iso'}
|
||||
|
||||
mock_deploy_utils.get_boot_option.return_value = 'ramdisk'
|
||||
|
||||
mock__prepare_boot_iso.return_value = 'image-url'
|
||||
|
||||
task.driver.boot.prepare_instance(task)
|
||||
|
||||
mock__prepare_boot_iso.assert_called_once_with(task)
|
||||
|
||||
mock__eject_vmedia.assert_called_once_with(
|
||||
task, sushy.VIRTUAL_MEDIA_CD)
|
||||
|
||||
mock__insert_vmedia.assert_called_once_with(
|
||||
task, 'image-url', sushy.VIRTUAL_MEDIA_CD)
|
||||
|
||||
mock_manager_utils.node_set_boot_device.assert_called_once_with(
|
||||
task, boot_devices.CDROM, persistent=True)
|
||||
|
||||
mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task)
|
||||
|
||||
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
|
||||
'clean_up_instance', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_prepare_boot_iso', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_insert_vmedia', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_parse_driver_info', autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'manager_utils', autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
||||
def test_prepare_instance_ramdisk_boot_iso_boot(
|
||||
self, mock_boot_mode_utils, mock_deploy_utils, mock_manager_utils,
|
||||
mock__parse_driver_info, mock__insert_vmedia, mock__eject_vmedia,
|
||||
mock__prepare_boot_iso, mock_clean_up_instance):
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.provision_state = states.DEPLOYING
|
||||
i_info = task.node.instance_info
|
||||
i_info['boot_iso'] = "super-magic"
|
||||
task.node.instance_info = i_info
|
||||
mock_deploy_utils.get_boot_option.return_value = 'ramdisk'
|
||||
|
||||
mock__prepare_boot_iso.return_value = 'image-url'
|
||||
|
||||
task.driver.boot.prepare_instance(task)
|
||||
|
||||
mock__prepare_boot_iso.assert_called_once_with(task)
|
||||
|
||||
mock__eject_vmedia.assert_called_once_with(
|
||||
task, sushy.VIRTUAL_MEDIA_CD)
|
||||
|
||||
mock__insert_vmedia.assert_called_once_with(
|
||||
task, 'image-url', sushy.VIRTUAL_MEDIA_CD)
|
||||
|
||||
mock_manager_utils.node_set_boot_device.assert_called_once_with(
|
||||
task, boot_devices.CDROM, persistent=True)
|
||||
|
||||
mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task)
|
||||
|
||||
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_cleanup_iso_image', autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'manager_utils', autospec=True)
|
||||
|
@ -1397,6 +1397,20 @@ class ValidateImagePropertiesTestCase(db_base.DbTestCase):
|
||||
inst_info, ['kernel', 'ramdisk'])
|
||||
self.assertEqual(expected_error, str(error))
|
||||
|
||||
def test_validate_image_properties_boot_iso_conflict(self):
|
||||
instance_info = {
|
||||
'image_source': 'http://ubuntu',
|
||||
'boot_iso': 'http://ubuntu.iso',
|
||||
}
|
||||
expected_error = ("An 'image_source' and 'boot_iso' "
|
||||
"parameter may not be specified at "
|
||||
"the same time.")
|
||||
error = self.assertRaises(exception.InvalidParameterValue,
|
||||
utils.validate_image_properties,
|
||||
self.context,
|
||||
instance_info, [])
|
||||
self.assertEqual(expected_error, str(error))
|
||||
|
||||
|
||||
class ValidateParametersTestCase(db_base.DbTestCase):
|
||||
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds functionality to allow a user to supply a node
|
||||
``instance_info/boot_iso`` parameter on machines utilizing the
|
||||
``redfish-virtual-media`` boot interface. When combined with the
|
||||
``ramdisk`` deployment interface, this allows an instance to boot
|
||||
into a user supplied ISO image.
|
Loading…
Reference in New Issue
Block a user