Adding changes for iso less vmedia support

This commit allows virtual media deployment/rescue
using kernel/ramdisk pair by creating iso on the fly
for ilo and ilo5 hardware types. Also it adds support
of dhcp less deploy to ilo and ilo5 hardware type.

Change-Id: I08e37a9f5268c15ba954b34ad706b900e5430da6
Story: #2006691
Task: #40893
This commit is contained in:
ankit 2020-09-15 06:49:20 +00:00 committed by Shivanand Tendulker
parent 484dcd5b60
commit b4fafd8878
8 changed files with 269 additions and 479 deletions

View File

@ -2162,6 +2162,11 @@ and ``ilo-uefi-https`` boot interface:
.. note::
UEFI secure boot is not supported with ``ilo-uefi-https`` boot interface.
Layer 3 or DHCP-less ramdisk booting
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DHCP-less deploy is supported by ``ilo`` and ``ilo5`` hardware types.
However it would work only with ilo-virtual-media boot interface. See
:ref:`dhcpless_booting` for more information.
.. _`ssacli documentation`: https://support.hpe.com/hpsc/doc/public/display?docId=c03909334
.. _`proliant-tools`: https://docs.openstack.org/diskimage-builder/latest/elements/proliant-tools/README.html

View File

@ -208,6 +208,9 @@ This initial interface does not support bootloader configuration
parameter injection, as such the ``[instance_info]/kernel_append_params``
setting is ignored.
.. _`dhcpless_booting`:
Layer 3 or DHCP-less ramdisk booting
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -216,7 +219,7 @@ service since it's a User Datagram Protocol (UDP) like other protocols used by
the PXE suite, there is no guarantee that packets will be delivered.
One of the solutions is the reliance on virtual media boot capability coupled
with another feature of ``redfish`` hardware type - its ability to provide
with another feature of hardware type - its ability to provide
network configuration that is placed in the config-drive_ of the node, the
configuration follows the same schema that OpenStack Nova uses for the
``network_data.json``. The config drive filesystem information is on the IPA

View File

@ -15,12 +15,9 @@
Boot Interface for iLO drivers and its supporting methods.
"""
import os
import tempfile
from urllib import parse as urlparse
from ironic_lib import metrics_utils
from ironic_lib import utils as ironic_utils
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
@ -32,7 +29,6 @@ from ironic.common.i18n import _
from ironic.common import image_service
from ironic.common import images
from ironic.common import states
from ironic.common import swift
from ironic.conductor import utils as manager_utils
from ironic.drivers import base
from ironic.drivers.modules import boot_mode_utils
@ -62,11 +58,6 @@ REQUIRED_PROPERTIES_UEFI_HTTPS_BOOT = {
"Required."),
'ilo_deploy_ramdisk': _("URL or Glance UUID of the ramdisk that is "
"mounted at boot time. Required."),
'ilo_bootloader': _("URL or Glance UUID of the EFI system partition "
"image containing EFI boot loader. This image will "
"be used by ironic when building UEFI-bootable ISO "
"out of kernel and ramdisk. Required for UEFI "
"boot from partition images.")
}
RESCUE_PROPERTIES_UEFI_HTTPS_BOOT = {
'ilo_rescue_kernel': _('URL or Glance UUID of the rescue kernel. This '
@ -74,6 +65,8 @@ RESCUE_PROPERTIES_UEFI_HTTPS_BOOT = {
'ilo_rescue_ramdisk': _('URL or Glance UUID of the rescue ramdisk with '
'agent that is used at node rescue time. '
'The value is required for rescue mode.'),
}
OPTIONAL_PROPERTIES = {
'ilo_bootloader': _("URL or Glance UUID of the EFI system partition "
"image containing EFI boot loader. This image will "
"be used by ironic when building UEFI-bootable ISO "
@ -82,6 +75,11 @@ RESCUE_PROPERTIES_UEFI_HTTPS_BOOT = {
}
COMMON_PROPERTIES = REQUIRED_PROPERTIES
KERNEL_RAMDISK_LABELS = {
'deploy': REQUIRED_PROPERTIES_UEFI_HTTPS_BOOT,
'rescue': RESCUE_PROPERTIES_UEFI_HTTPS_BOOT
}
def parse_driver_info(node, mode='deploy'):
"""Gets the driver specific Node deployment info.
@ -101,36 +99,43 @@ def parse_driver_info(node, mode='deploy'):
"""
info = node.driver_info
d_info = {}
if mode == 'rescue':
if mode == 'rescue' and info.get('ilo_rescue_iso'):
d_info['ilo_rescue_iso'] = info.get('ilo_rescue_iso')
else:
elif mode == 'deploy' and info.get('ilo_deploy_iso'):
d_info['ilo_deploy_iso'] = info.get('ilo_deploy_iso')
else:
params_to_check = KERNEL_RAMDISK_LABELS[mode]
d_info = {option: info.get(option)
for option in params_to_check}
if not any(d_info.values()):
# NOTE(dtantsur): avoid situation when e.g. deploy_kernel comes
# from driver_info but deploy_ramdisk comes from configuration,
# since it's a sign of a potential operator's mistake.
d_info = {k: getattr(CONF.conductor, k.replace('ilo_', ''))
for k in params_to_check}
error_msg = (_("Error validating iLO virtual media for %s. Some "
"parameters were missing in node's driver_info") % mode)
"parameters were missing in node's driver_info.") % mode)
deploy_utils.check_for_missing_params(d_info, error_msg)
d_info.update(
{k: info.get(k, getattr(CONF.conductor, k.replace('ilo_', ''), None))
for k in OPTIONAL_PROPERTIES})
return d_info
def _get_boot_iso_object_name(node):
"""Returns the boot iso object name for a given node.
:param node: the node for which object name is to be provided.
"""
return "boot-%s" % node.uuid
def _get_boot_iso(task, root_uuid):
"""This method returns a boot ISO to boot the node.
It chooses one of the three options in the order as below:
1. Does nothing if 'ilo_boot_iso' is present in node's instance_info and
'boot_iso_created_in_web_server' is not set in 'driver_internal_info'.
1. Does nothing if 'ilo_boot_iso' is present in node's instance_info.
2. Image deployed has a meta-property 'boot_iso' in Glance. This should
refer to the UUID of the boot_iso which exists in Glance.
3. Generates a boot ISO on the fly using kernel and ramdisk mentioned in
the image deployed. It uploads the generated boot ISO to Swift.
3. Returns a boot ISO created on the fly using kernel and ramdisk
mentioned in the image deployed.
:param task: a TaskManager instance containing the node to act on.
:param root_uuid: the uuid of the root partition.
@ -155,12 +160,7 @@ def _get_boot_iso(task, root_uuid):
# Option 1 - Check if user has provided ilo_boot_iso in node's
# instance_info
driver_internal_info = task.node.driver_internal_info
boot_iso_created_in_web_server = (
driver_internal_info.get('boot_iso_created_in_web_server'))
if (task.node.instance_info.get('ilo_boot_iso')
and not boot_iso_created_in_web_server):
if task.node.instance_info.get('ilo_boot_iso'):
LOG.debug("Using ilo_boot_iso provided in node's instance_info")
boot_iso = task.node.instance_info['ilo_boot_iso']
if not service_utils.is_glance_image(boot_iso):
@ -183,24 +183,14 @@ def _get_boot_iso(task, root_uuid):
image_href = deploy_info['image_source']
image_properties = (
images.get_image_properties(
task.context, image_href, ['boot_iso', 'kernel_id', 'ramdisk_id']))
task.context, image_href, ['boot_iso']))
boot_iso_uuid = image_properties.get('boot_iso')
kernel_href = (task.node.instance_info.get('kernel')
or image_properties.get('kernel_id'))
ramdisk_href = (task.node.instance_info.get('ramdisk')
or image_properties.get('ramdisk_id'))
if boot_iso_uuid:
LOG.debug("Found boot_iso %s in Glance", boot_iso_uuid)
return boot_iso_uuid
if not kernel_href or not ramdisk_href:
LOG.error("Unable to find kernel or ramdisk for "
"image %(image)s to generate boot ISO for %(node)s",
{'image': image_href, 'node': task.node.uuid})
return
# NOTE(rameshg87): Functionality to share the boot ISOs created for
# similar instances (instances with same deployed image) is
# not implemented as of now. Creation/Deletion of such a shared boot ISO
@ -209,70 +199,7 @@ def _get_boot_iso(task, root_uuid):
# Option 3 - Create boot_iso from kernel/ramdisk, upload to Swift
# or web server and provide its name.
deploy_iso_uuid = deploy_info['ilo_deploy_iso']
boot_mode = boot_mode_utils.get_boot_mode(task.node)
boot_iso_object_name = _get_boot_iso_object_name(task.node)
kernel_params = ""
if deploy_utils.get_boot_option(task.node) == "ramdisk":
i_info = task.node.instance_info
kernel_params = "root=/dev/ram0 text "
kernel_params += i_info.get("ramdisk_kernel_arguments", "")
else:
kernel_params = CONF.pxe.pxe_append_params
with tempfile.NamedTemporaryFile(dir=CONF.tempdir) as fileobj:
boot_iso_tmp_file = fileobj.name
images.create_boot_iso(task.context, boot_iso_tmp_file,
kernel_href, ramdisk_href,
deploy_iso_href=deploy_iso_uuid,
root_uuid=root_uuid,
kernel_params=kernel_params,
boot_mode=boot_mode)
if CONF.ilo.use_web_server_for_images:
boot_iso_url = (
ilo_common.copy_image_to_web_server(boot_iso_tmp_file,
boot_iso_object_name))
driver_internal_info = task.node.driver_internal_info
driver_internal_info['boot_iso_created_in_web_server'] = True
task.node.driver_internal_info = driver_internal_info
task.node.save()
LOG.debug("Created boot_iso %(boot_iso)s for node %(node)s",
{'boot_iso': boot_iso_url, 'node': task.node.uuid})
return boot_iso_url
else:
container = CONF.ilo.swift_ilo_container
swift_api = swift.SwiftAPI()
swift_api.create_object(container, boot_iso_object_name,
boot_iso_tmp_file)
LOG.debug("Created boot_iso %s in Swift", boot_iso_object_name)
return 'swift:%s' % boot_iso_object_name
def _clean_up_boot_iso_for_instance(node):
"""Deletes the boot ISO if it was created for the instance.
:param node: an ironic node object.
"""
ilo_boot_iso = node.instance_info.get('ilo_boot_iso')
if not ilo_boot_iso:
return
if ilo_boot_iso.startswith('swift'):
swift_api = swift.SwiftAPI()
container = CONF.ilo.swift_ilo_container
boot_iso_object_name = _get_boot_iso_object_name(node)
try:
swift_api.delete_object(container, boot_iso_object_name)
except exception.SwiftOperationError as e:
LOG.exception("Failed to clean up boot ISO for node "
"%(node)s. Error: %(error)s.",
{'node': node.uuid, 'error': e})
elif CONF.ilo.use_web_server_for_images:
result = urlparse.urlparse(ilo_boot_iso)
ilo_boot_iso_name = os.path.basename(result.path)
boot_iso_path = os.path.join(
CONF.deploy.http_root, ilo_boot_iso_name)
ironic_utils.unlink_without_raise(boot_iso_path)
return image_utils.prepare_boot_iso(task, deploy_info, root_uuid)
def _parse_deploy_info(node):
@ -308,19 +235,7 @@ def _validate_driver_info(task):
"""
node = task.node
ilo_common.parse_driver_info(node)
if 'ilo_deploy_iso' not in node.driver_info:
raise exception.MissingParameterValue(_(
"Missing 'ilo_deploy_iso' parameter in node's 'driver_info'."))
deploy_iso = node.driver_info['ilo_deploy_iso']
if not service_utils.is_glance_image(deploy_iso):
try:
image_service.HttpImageService().validate_href(deploy_iso)
except exception.ImageRefValidationFailed:
raise exception.InvalidParameterValue(_(
"Virtual media boot accepts only Glance images or "
"HTTP(S) as driver_info['ilo_deploy_iso']. Either '%s' "
"is not a glance UUID or not a valid HTTP(S) URL or "
"the given URL is not reachable.") % deploy_iso)
parse_driver_info(node)
def _validate_instance_image_info(task):
@ -542,12 +457,19 @@ class IloVirtualMediaBoot(base.BootInterface):
deploy_nic_mac = deploy_utils.get_single_nic_with_vif_port_id(task)
ramdisk_params['BOOTIF'] = deploy_nic_mac
if node.provision_state == states.RESCUING:
if (node.driver_info.get('ilo_rescue_iso')
and node.provision_state == states.RESCUING):
iso = node.driver_info['ilo_rescue_iso']
else:
iso = node.driver_info['ilo_deploy_iso']
ilo_common.setup_vmedia(task, iso, ramdisk_params)
elif node.driver_info.get('ilo_deploy_iso'):
iso = node.driver_info['ilo_deploy_iso']
ilo_common.setup_vmedia(task, iso, ramdisk_params)
else:
mode = deploy_utils.rescue_or_deploy_mode(node)
d_info = parse_driver_info(node, mode)
iso = image_utils.prepare_deploy_iso(task, ramdisk_params,
mode, d_info)
ilo_common.setup_vmedia(task, iso)
@METRICS.timer('IloVirtualMediaBoot.prepare_instance')
def prepare_instance(self, task):
@ -635,19 +557,18 @@ 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)
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
task.driver.management.clear_iscsi_boot_target(task)
driver_internal_info = task.node.driver_internal_info
driver_internal_info.pop('ilo_uefi_iscsi_boot', None)
else:
_clean_up_boot_iso_for_instance(task.node)
driver_internal_info.pop('boot_iso_created_in_web_server', None)
ilo_common.cleanup_vmedia_boot(task)
task.node.driver_internal_info = driver_internal_info
task.node.save()
else:
image_utils.cleanup_iso_image(task)
ilo_common.cleanup_vmedia_boot(task)
@METRICS.timer('IloVirtualMediaBoot.clean_up_ramdisk')
def clean_up_ramdisk(self, task):
@ -662,6 +583,12 @@ class IloVirtualMediaBoot(base.BootInterface):
"""
ilo_common.cleanup_vmedia_boot(task)
info = task.node.driver_info
mode = deploy_utils.rescue_or_deploy_mode(task.node)
if ((mode == 'rescue' and not info.get('ilo_rescue_iso'))
or (mode == 'deploy' and not info.get('ilo_deploy_iso'))):
image_utils.cleanup_iso_image(task)
def _configure_vmedia_boot(self, task, root_uuid):
"""Configure vmedia boot for the node.
@ -978,6 +905,18 @@ class IloUefiHttpsBoot(base.BootInterface):
deploy_info = {option: info.get(option)
for option in params_to_check}
if not any(deploy_info.values()):
# NOTE(dtantsur): avoid situation when e.g. deploy_kernel comes
# from driver_info but deploy_ramdisk comes from configuration,
# since it's a sign of a potential operator's mistake.
deploy_info = {k: getattr(CONF.conductor, k.replace('ilo_', ''))
for k in params_to_check}
deploy_info.update(
{k: info.get(k, getattr(CONF.conductor,
k.replace('ilo_', ''), None))
for k in OPTIONAL_PROPERTIES})
self._validate_hrefs(deploy_info)
error_msg = (_("Error validating %s for iLO UEFI HTTPS boot. Some "

View File

@ -645,7 +645,7 @@ def update_boot_mode(task):
node.save()
def setup_vmedia(task, iso, ramdisk_options):
def setup_vmedia(task, iso, ramdisk_options=None):
"""Attaches virtual media and sets it as boot device.
This method attaches the given bootable ISO as virtual media, prepares the

View File

@ -43,14 +43,24 @@ class ImageHandler(object):
"container": CONF.redfish.swift_container,
"timeout": CONF.redfish.swift_object_expiry_timeout,
"image_subdir": "redfish",
"file_permission": CONF.redfish.file_permission
"file_permission": CONF.redfish.file_permission,
"kernel_params": CONF.redfish.kernel_append_params
},
"ilo5": {
"swift_enabled": not CONF.ilo.use_web_server_for_images,
"container": CONF.ilo.swift_ilo_container,
"timeout": CONF.ilo.swift_object_expiry_timeout,
"image_subdir": "ilo",
"file_permission": CONF.ilo.file_permission
"file_permission": CONF.ilo.file_permission,
"kernel_params": CONF.pxe.pxe_append_params
},
"ilo": {
"swift_enabled": not CONF.ilo.use_web_server_for_images,
"container": CONF.ilo.swift_ilo_container,
"timeout": CONF.ilo.swift_object_expiry_timeout,
"image_subdir": "ilo",
"file_permission": CONF.ilo.file_permission,
"kernel_params": CONF.pxe.pxe_append_params
},
}
@ -60,6 +70,8 @@ class ImageHandler(object):
self._timeout = self._SWIFT_MAP[driver].get("timeout")
self._image_subdir = self._SWIFT_MAP[driver].get("image_subdir")
self._file_permission = self._SWIFT_MAP[driver].get("file_permission")
# To get the kernel parameters
self.kernel_params = self._SWIFT_MAP[driver].get("kernel_params")
def _is_swift_enabled(self):
try:
@ -303,6 +315,9 @@ def _prepare_iso_image(task, kernel_href, ramdisk_href,
"building ISO, or explicit ISO for %(node)s") %
{'node': task.node.uuid})
img_handler = ImageHandler(task.node.driver)
k_param = img_handler.kernel_params
i_info = task.node.instance_info
# NOTE(TheJulia): Until we support modifying a base iso, most of
@ -315,8 +330,7 @@ def _prepare_iso_image(task, kernel_href, ramdisk_href,
kernel_params = None
else:
kernel_params = i_info.get(
'kernel_append_params', CONF.redfish.kernel_append_params)
kernel_params = i_info.get('kernel_append_params', k_param)
if params and not base_iso:
kernel_params = ' '.join(
@ -374,7 +388,6 @@ def _prepare_iso_image(task, kernel_href, ramdisk_href,
iso_object_name = _get_iso_image_name(task.node)
img_handler = ImageHandler(task.node.driver)
image_url = img_handler.publish_image(
boot_iso_tmp_file, iso_object_name)

View File

@ -15,12 +15,9 @@
"""Test class for boot methods used by iLO modules."""
import io
import tempfile
from unittest import mock
from urllib import parse as urlparse
from ironic_lib import utils as ironic_utils
from oslo_config import cfg
from ironic.common import boot_devices
@ -29,7 +26,6 @@ from ironic.common.glance_service import service_utils
from ironic.common import image_service
from ironic.common import images
from ironic.common import states
from ironic.common import swift
from ironic.conductor import task_manager
from ironic.conductor import utils as manager_utils
from ironic.drivers.modules import boot_mode_utils
@ -56,9 +52,74 @@ class IloBootCommonMethodsTestCase(test_common.BaseIloTest):
boot_interface = 'ilo-virtual-media'
def test_parse_driver_info(self):
def test_parse_driver_info_deploy_iso(self):
self.node.driver_info['ilo_deploy_iso'] = 'deploy-iso'
expected_driver_info = {'ilo_deploy_iso': 'deploy-iso'}
expected_driver_info = {'ilo_bootloader': None,
'ilo_deploy_iso': 'deploy-iso'}
actual_driver_info = ilo_boot.parse_driver_info(self.node)
self.assertEqual(expected_driver_info, actual_driver_info)
def test_parse_driver_info_rescue_iso(self):
self.node.driver_info['ilo_rescue_iso'] = 'rescue-iso'
expected_driver_info = {'ilo_bootloader': None,
'ilo_rescue_iso': 'rescue-iso'}
actual_driver_info = ilo_boot.parse_driver_info(self.node, 'rescue')
self.assertEqual(expected_driver_info, actual_driver_info)
def test_parse_driver_info_deploy(self):
self.node.driver_info['ilo_deploy_kernel'] = 'kernel'
self.node.driver_info['ilo_deploy_ramdisk'] = 'ramdisk'
self.node.driver_info['ilo_bootloader'] = 'bootloader'
expected_driver_info = {'ilo_deploy_kernel': 'kernel',
'ilo_deploy_ramdisk': 'ramdisk',
'ilo_bootloader': 'bootloader'}
actual_driver_info = ilo_boot.parse_driver_info(self.node)
self.assertEqual(expected_driver_info, actual_driver_info)
def test_parse_driver_info_rescue(self):
self.node.driver_info['ilo_rescue_kernel'] = 'kernel'
self.node.driver_info['ilo_rescue_ramdisk'] = 'ramdisk'
self.node.driver_info['ilo_bootloader'] = 'bootloader'
expected_driver_info = {'ilo_rescue_kernel': 'kernel',
'ilo_rescue_ramdisk': 'ramdisk',
'ilo_bootloader': 'bootloader'}
actual_driver_info = ilo_boot.parse_driver_info(self.node, 'rescue')
self.assertEqual(expected_driver_info, actual_driver_info)
def test_parse_driver_info_deploy_config(self):
CONF.conductor.deploy_kernel = 'kernel'
CONF.conductor.deploy_ramdisk = 'ramdisk'
CONF.conductor.bootloader = 'bootloader'
expected_driver_info = {'ilo_deploy_kernel': 'kernel',
'ilo_deploy_ramdisk': 'ramdisk',
'ilo_bootloader': 'bootloader'}
actual_driver_info = ilo_boot.parse_driver_info(self.node)
self.assertEqual(expected_driver_info, actual_driver_info)
def test_parse_driver_info_rescue_config(self):
CONF.conductor.rescue_kernel = 'kernel'
CONF.conductor.rescue_ramdisk = 'ramdisk'
CONF.conductor.bootloader = 'bootloader'
expected_driver_info = {'ilo_rescue_kernel': 'kernel',
'ilo_rescue_ramdisk': 'ramdisk',
'ilo_bootloader': 'bootloader'}
actual_driver_info = ilo_boot.parse_driver_info(self.node, 'rescue')
self.assertEqual(expected_driver_info, actual_driver_info)
def test_parse_driver_info_bootloader_none(self):
CONF.conductor.deploy_kernel = 'kernel'
CONF.conductor.deploy_ramdisk = 'ramdisk'
expected_driver_info = {'ilo_deploy_kernel': 'kernel',
'ilo_deploy_ramdisk': 'ramdisk',
'ilo_bootloader': None}
actual_driver_info = ilo_boot.parse_driver_info(self.node)
self.assertEqual(expected_driver_info, actual_driver_info)
@ -72,11 +133,6 @@ class IloBootPrivateMethodsTestCase(test_common.BaseIloTest):
boot_interface = 'ilo-virtual-media'
def test__get_boot_iso_object_name(self):
boot_iso_actual = ilo_boot._get_boot_iso_object_name(self.node)
boot_iso_expected = "boot-%s" % self.node.uuid
self.assertEqual(boot_iso_expected, boot_iso_actual)
@mock.patch.object(image_service.HttpImageService, 'validate_href',
spec_set=True, autospec=True)
def test__get_boot_iso_http_url(self, service_mock):
@ -116,294 +172,44 @@ class IloBootPrivateMethodsTestCase(test_common.BaseIloTest):
image_props_mock):
deploy_info_mock.return_value = {'image_source': 'image-uuid',
'ilo_deploy_iso': 'deploy_iso_uuid'}
image_props_mock.return_value = {'boot_iso': u'glance://uui\u0111',
'kernel_id': None,
'ramdisk_id': None}
image_props_mock.return_value = {'boot_iso': u'glance://uui\u0111'}
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
driver_internal_info = task.node.driver_internal_info
driver_internal_info['boot_iso_created_in_web_server'] = False
task.node.driver_internal_info = driver_internal_info
task.node.save()
boot_iso_actual = ilo_boot._get_boot_iso(task, 'root-uuid')
deploy_info_mock.assert_called_once_with(task.node)
image_props_mock.assert_called_once_with(
task.context, 'image-uuid',
['boot_iso', 'kernel_id', 'ramdisk_id'])
task.context, 'image-uuid', ['boot_iso'])
boot_iso_expected = u'glance://uui\u0111'
self.assertEqual(boot_iso_expected, boot_iso_actual)
@mock.patch.object(boot_mode_utils, 'get_boot_mode_for_deploy',
spec_set=True, autospec=True)
@mock.patch.object(ilo_boot.LOG, 'error', spec_set=True, autospec=True)
@mock.patch.object(images, 'get_image_properties', spec_set=True,
autospec=True)
@mock.patch.object(ilo_boot, '_parse_deploy_info', spec_set=True,
autospec=True)
def test__get_boot_iso_uefi_no_glance_image(self,
deploy_info_mock,
image_props_mock,
log_mock,
boot_mode_mock):
deploy_info_mock.return_value = {'image_source': 'image-uuid',
'ilo_deploy_iso': 'deploy_iso_uuid'}
image_props_mock.return_value = {'boot_iso': None,
'kernel_id': None,
'ramdisk_id': None}
properties = {'capabilities': 'boot_mode:uefi'}
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.node.properties = properties
boot_iso_result = ilo_boot._get_boot_iso(task, 'root-uuid')
deploy_info_mock.assert_called_once_with(task.node)
image_props_mock.assert_called_once_with(
task.context, 'image-uuid',
['boot_iso', 'kernel_id', 'ramdisk_id'])
self.assertTrue(log_mock.called)
self.assertFalse(boot_mode_mock.called)
self.assertIsNone(boot_iso_result)
@mock.patch.object(tempfile, 'NamedTemporaryFile', spec_set=True,
autospec=True)
@mock.patch.object(images, 'create_boot_iso', spec_set=True, autospec=True)
@mock.patch.object(swift, 'SwiftAPI', spec_set=True, autospec=True)
@mock.patch.object(ilo_boot, '_get_boot_iso_object_name', spec_set=True,
autospec=True)
@mock.patch.object(driver_utils, 'get_node_capability', spec_set=True,
@mock.patch.object(image_utils, 'prepare_boot_iso', spec_set=True,
autospec=True)
@mock.patch.object(images, 'get_image_properties', spec_set=True,
autospec=True)
@mock.patch.object(ilo_boot, '_parse_deploy_info', spec_set=True,
autospec=True)
def test__get_boot_iso_create(self, deploy_info_mock, image_props_mock,
capability_mock, boot_object_name_mock,
swift_api_mock,
create_boot_iso_mock, tempfile_mock):
CONF.ilo.swift_ilo_container = 'ilo-cont'
CONF.pxe.pxe_append_params = 'kernel-params'
swift_obj_mock = swift_api_mock.return_value
fileobj_mock = mock.MagicMock(spec=io.BytesIO)
fileobj_mock.name = 'tmpfile'
mock_file_handle = mock.MagicMock(spec=io.BytesIO)
mock_file_handle.__enter__.return_value = fileobj_mock
tempfile_mock.return_value = mock_file_handle
prepare_iso_mock):
deploy_info_mock.return_value = {'image_source': 'image-uuid',
'ilo_deploy_iso': 'deploy_iso_uuid'}
image_props_mock.return_value = {'boot_iso': None,
'kernel_id': 'kernel_uuid',
'ramdisk_id': 'ramdisk_uuid'}
boot_object_name_mock.return_value = 'abcdef'
create_boot_iso_mock.return_value = '/path/to/boot-iso'
capability_mock.return_value = 'uefi'
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
boot_iso_actual = ilo_boot._get_boot_iso(task, 'root-uuid')
deploy_info_mock.assert_called_once_with(task.node)
image_props_mock.assert_called_once_with(
task.context, 'image-uuid',
['boot_iso', 'kernel_id', 'ramdisk_id'])
boot_object_name_mock.assert_called_once_with(task.node)
create_boot_iso_mock.assert_called_once_with(
task.context, 'tmpfile', 'kernel_uuid', 'ramdisk_uuid',
deploy_iso_href='deploy_iso_uuid',
root_uuid='root-uuid',
kernel_params='kernel-params',
boot_mode='uefi')
swift_obj_mock.create_object.assert_called_once_with('ilo-cont',
'abcdef',
'tmpfile')
boot_iso_expected = 'swift:abcdef'
self.assertEqual(boot_iso_expected, boot_iso_actual)
image_props_mock.return_value = {'boot_iso': None}
@mock.patch.object(ilo_common, 'copy_image_to_web_server', spec_set=True,
autospec=True)
@mock.patch.object(tempfile, 'NamedTemporaryFile', spec_set=True,
autospec=True)
@mock.patch.object(images, 'create_boot_iso', spec_set=True, autospec=True)
@mock.patch.object(ilo_boot, '_get_boot_iso_object_name', spec_set=True,
autospec=True)
@mock.patch.object(driver_utils, 'get_node_capability', spec_set=True,
autospec=True)
@mock.patch.object(images, 'get_image_properties', spec_set=True,
autospec=True)
@mock.patch.object(ilo_boot, '_parse_deploy_info', spec_set=True,
autospec=True)
def test__get_boot_iso_recreate_boot_iso_use_webserver(
self, deploy_info_mock, image_props_mock,
capability_mock, boot_object_name_mock,
create_boot_iso_mock, tempfile_mock,
copy_file_mock):
CONF.ilo.swift_ilo_container = 'ilo-cont'
CONF.ilo.use_web_server_for_images = True
CONF.deploy.http_url = "http://10.10.1.30/httpboot"
CONF.deploy.http_root = "/httpboot"
CONF.pxe.pxe_append_params = 'kernel-params'
fileobj_mock = mock.MagicMock(spec=io.BytesIO)
fileobj_mock.name = 'tmpfile'
mock_file_handle = mock.MagicMock(spec=io.BytesIO)
mock_file_handle.__enter__.return_value = fileobj_mock
tempfile_mock.return_value = mock_file_handle
ramdisk_href = "http://10.10.1.30/httpboot/ramdisk"
kernel_href = "http://10.10.1.30/httpboot/kernel"
deploy_info_mock.return_value = {'image_source': 'image-uuid',
prepare_iso_mock.return_value = 'swift:boot-iso'
d_info = {'image_source': 'image-uuid',
'ilo_deploy_iso': 'deploy_iso_uuid'}
image_props_mock.return_value = {'boot_iso': None,
'kernel_id': kernel_href,
'ramdisk_id': ramdisk_href}
boot_object_name_mock.return_value = 'new_boot_iso'
create_boot_iso_mock.return_value = '/path/to/boot-iso'
capability_mock.return_value = 'uefi'
copy_file_mock.return_value = "http://10.10.1.30/httpboot/new_boot_iso"
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
driver_internal_info = task.node.driver_internal_info
driver_internal_info['boot_iso_created_in_web_server'] = True
instance_info = task.node.instance_info
old_boot_iso = 'http://10.10.1.30/httpboot/old_boot_iso'
instance_info['ilo_boot_iso'] = old_boot_iso
boot_iso_actual = ilo_boot._get_boot_iso(task, 'root-uuid')
deploy_info_mock.assert_called_once_with(task.node)
image_props_mock.assert_called_once_with(
task.context, 'image-uuid',
['boot_iso', 'kernel_id', 'ramdisk_id'])
boot_object_name_mock.assert_called_once_with(task.node)
create_boot_iso_mock.assert_called_once_with(
task.context, 'tmpfile', kernel_href, ramdisk_href,
deploy_iso_href='deploy_iso_uuid',
root_uuid='root-uuid',
kernel_params='kernel-params',
boot_mode='uefi')
boot_iso_expected = 'http://10.10.1.30/httpboot/new_boot_iso'
self.assertEqual(boot_iso_expected, boot_iso_actual)
copy_file_mock.assert_called_once_with(fileobj_mock.name,
'new_boot_iso')
@mock.patch.object(ilo_common, 'copy_image_to_web_server', spec_set=True,
autospec=True)
@mock.patch.object(tempfile, 'NamedTemporaryFile', spec_set=True,
autospec=True)
@mock.patch.object(images, 'create_boot_iso', spec_set=True, autospec=True)
@mock.patch.object(ilo_boot, '_get_boot_iso_object_name', spec_set=True,
autospec=True)
@mock.patch.object(driver_utils, 'get_node_capability', spec_set=True,
autospec=True)
@mock.patch.object(images, 'get_image_properties', spec_set=True,
autospec=True)
@mock.patch.object(ilo_boot, '_parse_deploy_info', spec_set=True,
autospec=True)
def test__get_boot_iso_create_use_webserver_true_ramdisk_webserver(
self, deploy_info_mock, image_props_mock,
capability_mock, boot_object_name_mock,
create_boot_iso_mock, tempfile_mock,
copy_file_mock):
CONF.ilo.swift_ilo_container = 'ilo-cont'
CONF.ilo.use_web_server_for_images = True
CONF.deploy.http_url = "http://10.10.1.30/httpboot"
CONF.deploy.http_root = "/httpboot"
CONF.pxe.pxe_append_params = 'kernel-params'
fileobj_mock = mock.MagicMock(spec=io.BytesIO)
fileobj_mock.name = 'tmpfile'
mock_file_handle = mock.MagicMock(spec=io.BytesIO)
mock_file_handle.__enter__.return_value = fileobj_mock
tempfile_mock.return_value = mock_file_handle
ramdisk_href = "http://10.10.1.30/httpboot/ramdisk"
kernel_href = "http://10.10.1.30/httpboot/kernel"
deploy_info_mock.return_value = {'image_source': 'image-uuid',
'ilo_deploy_iso': 'deploy_iso_uuid'}
image_props_mock.return_value = {'boot_iso': None,
'kernel_id': kernel_href,
'ramdisk_id': ramdisk_href}
boot_object_name_mock.return_value = 'abcdef'
create_boot_iso_mock.return_value = '/path/to/boot-iso'
capability_mock.return_value = 'uefi'
copy_file_mock.return_value = "http://10.10.1.30/httpboot/abcdef"
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
boot_iso_actual = ilo_boot._get_boot_iso(task, 'root-uuid')
deploy_info_mock.assert_called_once_with(task.node)
image_props_mock.assert_called_once_with(
task.context, 'image-uuid',
['boot_iso', 'kernel_id', 'ramdisk_id'])
boot_object_name_mock.assert_called_once_with(task.node)
create_boot_iso_mock.assert_called_once_with(
task.context, 'tmpfile', kernel_href, ramdisk_href,
deploy_iso_href='deploy_iso_uuid',
root_uuid='root-uuid',
kernel_params='kernel-params',
boot_mode='uefi')
boot_iso_expected = 'http://10.10.1.30/httpboot/abcdef'
task.context, 'image-uuid', ['boot_iso'])
prepare_iso_mock.assert_called_once_with(
task, d_info, 'root-uuid')
boot_iso_expected = 'swift:boot-iso'
self.assertEqual(boot_iso_expected, boot_iso_actual)
copy_file_mock.assert_called_once_with(fileobj_mock.name,
'abcdef')
@mock.patch.object(ilo_boot, '_get_boot_iso_object_name', spec_set=True,
autospec=True)
@mock.patch.object(swift, 'SwiftAPI', spec_set=True, autospec=True)
def test__clean_up_boot_iso_for_instance(self, swift_mock,
boot_object_name_mock):
swift_obj_mock = swift_mock.return_value
CONF.ilo.swift_ilo_container = 'ilo-cont'
boot_object_name_mock.return_value = 'boot-object'
i_info = self.node.instance_info
i_info['ilo_boot_iso'] = 'swift:bootiso'
self.node.instance_info = i_info
self.node.save()
ilo_boot._clean_up_boot_iso_for_instance(self.node)
swift_obj_mock.delete_object.assert_called_once_with('ilo-cont',
'boot-object')
@mock.patch.object(ilo_boot.LOG, 'exception', spec_set=True,
autospec=True)
@mock.patch.object(ilo_boot, '_get_boot_iso_object_name', spec_set=True,
autospec=True)
@mock.patch.object(swift, 'SwiftAPI', spec_set=True, autospec=True)
def test__clean_up_boot_iso_for_instance_exc(self, swift_mock,
boot_object_name_mock,
log_mock):
swift_obj_mock = swift_mock.return_value
exc = exception.SwiftObjectNotFoundError('error')
swift_obj_mock.delete_object.side_effect = exc
CONF.ilo.swift_ilo_container = 'ilo-cont'
boot_object_name_mock.return_value = 'boot-object'
i_info = self.node.instance_info
i_info['ilo_boot_iso'] = 'swift:bootiso'
self.node.instance_info = i_info
self.node.save()
ilo_boot._clean_up_boot_iso_for_instance(self.node)
swift_obj_mock.delete_object.assert_called_once_with('ilo-cont',
'boot-object')
self.assertTrue(log_mock.called)
@mock.patch.object(ironic_utils, 'unlink_without_raise', spec_set=True,
autospec=True)
def test__clean_up_boot_iso_for_instance_on_webserver(self, unlink_mock):
CONF.ilo.use_web_server_for_images = True
CONF.deploy.http_root = "/webserver"
i_info = self.node.instance_info
i_info['ilo_boot_iso'] = 'http://x.y.z.a/webserver/boot-object'
self.node.instance_info = i_info
self.node.save()
boot_iso_path = "/webserver/boot-object"
ilo_boot._clean_up_boot_iso_for_instance(self.node)
unlink_mock.assert_called_once_with(boot_iso_path)
@mock.patch.object(ilo_boot, '_get_boot_iso_object_name', spec_set=True,
autospec=True)
def test__clean_up_boot_iso_for_instance_no_boot_iso(
self, boot_object_name_mock):
ilo_boot._clean_up_boot_iso_for_instance(self.node)
self.assertFalse(boot_object_name_mock.called)
@mock.patch.object(ilo_boot, 'parse_driver_info', spec_set=True,
autospec=True)
@ -418,68 +224,15 @@ class IloBootPrivateMethodsTestCase(test_common.BaseIloTest):
@mock.patch.object(ilo_common, 'parse_driver_info', spec_set=True,
autospec=True)
def test__validate_driver_info_MissingParam(self, mock_parse_driver_info):
@mock.patch.object(ilo_boot, 'parse_driver_info', spec_set=True,
autospec=True)
def test__validate_driver_info(self, mock_driver_info,
mock_parse_driver_info):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
self.assertRaisesRegex(exception.MissingParameterValue,
"Missing 'ilo_deploy_iso'",
ilo_boot._validate_driver_info, task)
mock_parse_driver_info.assert_called_once_with(task.node)
@mock.patch.object(service_utils, 'is_glance_image', spec_set=True,
autospec=True)
@mock.patch.object(ilo_common, 'parse_driver_info', spec_set=True,
autospec=True)
def test__validate_driver_info_valid_uuid(self, mock_parse_driver_info,
mock_is_glance_image):
mock_is_glance_image.return_value = True
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
deploy_iso = '8a81759a-f29b-454b-8ab3-161c6ca1882c'
task.node.driver_info['ilo_deploy_iso'] = deploy_iso
ilo_boot._validate_driver_info(task)
mock_parse_driver_info.assert_called_once_with(task.node)
mock_is_glance_image.assert_called_once_with(deploy_iso)
@mock.patch.object(image_service.HttpImageService, 'validate_href',
spec_set=True, autospec=True)
@mock.patch.object(service_utils, 'is_glance_image', spec_set=True,
autospec=True)
@mock.patch.object(ilo_common, 'parse_driver_info', spec_set=True,
autospec=True)
def test__validate_driver_info_InvalidParam(self, mock_parse_driver_info,
mock_is_glance_image,
mock_validate_href):
deploy_iso = 'http://abc.org/image/qcow2'
mock_validate_href.side_effect = exception.ImageRefValidationFailed(
image_href='http://abc.org/image/qcow2', reason='fail')
mock_is_glance_image.return_value = False
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.node.driver_info['ilo_deploy_iso'] = deploy_iso
self.assertRaisesRegex(exception.InvalidParameterValue,
"Virtual media boot accepts",
ilo_boot._validate_driver_info, task)
mock_parse_driver_info.assert_called_once_with(task.node)
mock_validate_href.assert_called_once_with(mock.ANY, deploy_iso)
@mock.patch.object(image_service.HttpImageService, 'validate_href',
spec_set=True, autospec=True)
@mock.patch.object(service_utils, 'is_glance_image', spec_set=True,
autospec=True)
@mock.patch.object(ilo_common, 'parse_driver_info', spec_set=True,
autospec=True)
def test__validate_driver_info_valid_url(self, mock_parse_driver_info,
mock_is_glance_image,
mock_validate_href):
deploy_iso = 'http://abc.org/image/deploy.iso'
mock_is_glance_image.return_value = False
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.node.driver_info['ilo_deploy_iso'] = deploy_iso
ilo_boot._validate_driver_info(task)
mock_parse_driver_info.assert_called_once_with(task.node)
mock_validate_href.assert_called_once_with(mock.ANY, deploy_iso)
mock_driver_info.assert_called_once_with(task.node)
@mock.patch.object(deploy_utils, 'validate_image_properties',
spec_set=True, autospec=True)
@ -907,6 +660,48 @@ class IloVirtualMediaBootTestCase(test_common.BaseIloTest):
self.assertEqual('http://mybootiso',
self.node.instance_info['ilo_boot_iso'])
@mock.patch.object(ilo_boot, 'prepare_node_for_deploy',
spec_set=True, autospec=True)
@mock.patch.object(ilo_common, 'eject_vmedia_devices',
spec_set=True, autospec=True)
@mock.patch.object(ilo_common, 'setup_vmedia', spec_set=True,
autospec=True)
@mock.patch.object(deploy_utils, 'get_single_nic_with_vif_port_id',
spec_set=True, autospec=True)
@mock.patch.object(deploy_utils, 'rescue_or_deploy_mode',
spec_set=True, autospec=True)
@mock.patch.object(ilo_boot, 'parse_driver_info', spec_set=True,
autospec=True)
@mock.patch.object(image_utils, 'prepare_deploy_iso',
spec_set=True, autospec=True)
def test_prepare_ramdisk_not_iso(
self, prepare_deploy_iso_mock, driver_info_mock,
mode_mock, get_nic_mock, setup_vmedia_mock,
eject_mock, prepare_node_for_deploy_mock):
self.node.provision_state = states.DEPLOYING
self.node.save()
mode = 'deploy'
ramdisk_params = {'a': 'b'}
d_info = {
'ilo_deploy_kernel': 'kernel',
'ilo_deploy_ramdisk': 'ramdisk',
'ilo_bootloader': 'bootloader'
}
driver_info_mock.return_value = d_info
prepare_deploy_iso_mock.return_value = 'recreated-iso'
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
mode_mock.return_value = 'deploy'
get_nic_mock.return_value = '12:34:56:78:90:ab'
task.driver.boot.prepare_ramdisk(task, ramdisk_params)
prepare_node_for_deploy_mock.assert_called_once_with(task)
eject_mock.assert_called_once_with(task)
driver_info_mock.assert_called_once_with(task.node, mode)
prepare_deploy_iso_mock.assert_called_once_with(
task, ramdisk_params, mode, d_info)
setup_vmedia_mock.assert_called_once_with(task, 'recreated-iso')
@mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
autospec=True)
@mock.patch.object(ilo_common, 'setup_vmedia_for_boot', spec_set=True,
@ -963,8 +758,8 @@ class IloVirtualMediaBootTestCase(test_common.BaseIloTest):
autospec=True)
@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(image_utils, 'cleanup_iso_image', 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,
@ -972,16 +767,13 @@ class IloVirtualMediaBootTestCase(test_common.BaseIloTest):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
driver_internal_info = task.node.driver_internal_info
driver_internal_info['boot_iso_created_in_web_server'] = False
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_iso_mock.assert_called_once_with(task)
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)
node_power_mock.assert_called_once_with(task,
states.POWER_OFF)
update_secure_boot_mode_mock.assert_called_once_with(task, False)
@ -1024,8 +816,8 @@ class IloVirtualMediaBootTestCase(test_common.BaseIloTest):
@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(image_utils, 'cleanup_iso_image', 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,
@ -1039,22 +831,27 @@ class IloVirtualMediaBootTestCase(test_common.BaseIloTest):
shared=False) as task:
is_iscsi_boot_mock.return_value = False
task.driver.boot.clean_up_instance(task)
cleanup_iso_mock.assert_called_once_with(task.node)
cleanup_iso_mock.assert_called_once_with(task)
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)
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(ilo_common, 'cleanup_vmedia_boot', spec_set=True,
autospec=True)
def test_clean_up_ramdisk(self, cleanup_vmedia_mock):
@mock.patch.object(deploy_utils, 'rescue_or_deploy_mode',
spec_set=True, autospec=True)
@mock.patch.object(image_utils, 'cleanup_iso_image', spec_set=True,
autospec=True)
def test_clean_up_ramdisk(self, cleanup_iso_mock, mode_mock,
cleanup_vmedia_mock):
mode_mock.return_value = 'deploy'
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.boot.clean_up_ramdisk(task)
cleanup_vmedia_mock.assert_called_once_with(task)
mode_mock.assert_called_once_with(task.node)
cleanup_iso_mock.assert_called_once_with(task)
@mock.patch.object(deploy_utils, 'is_iscsi_boot',
spec_set=True, autospec=True)
@ -1234,7 +1031,7 @@ class IloVirtualMediaBootTestCase(test_common.BaseIloTest):
def test_validate_rescue_no_rescue_ramdisk(self):
with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaisesRegex(exception.MissingParameterValue,
'Missing.*ilo_rescue_iso',
'Some parameters were missing*',
task.driver.boot.validate_rescue, task)

View File

@ -1079,6 +1079,21 @@ class IloCommonMethodsTestCase(BaseIloTest):
func_set_boot_device.assert_called_once_with(task,
boot_devices.CDROM)
@mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
autospec=True)
@mock.patch.object(ilo_common, 'setup_vmedia_for_boot', spec_set=True,
autospec=True)
def test_setup_vmedia_without_params(self,
func_setup_vmedia_for_boot,
func_set_boot_device):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
iso = '733d1c44-a2ea-414b-aca7-69decf20d810'
ilo_common.setup_vmedia(task, iso)
func_setup_vmedia_for_boot.assert_called_once_with(task, iso, None)
func_set_boot_device.assert_called_once_with(task,
boot_devices.CDROM)
@mock.patch.object(deploy_utils, 'is_secure_boot_requested', spec_set=True,
autospec=True)
@mock.patch.object(ilo_common, 'set_secure_boot_mode', spec_set=True,

View File

@ -0,0 +1,18 @@
---
features:
- |
Adds functionality to the ``ilo`` and ``ilo5`` hardware types
by enabling virtual media boot without user-built
deploy/rescue/boot ISO images. Instead, ironic will
build necessary images out of common kernel/ramdisk
pair (though user needs to provide ESP image).
User provided deploy/rescue/boot ISO images are
also supported.
- |
Adds support of DHCP less deploy to ``ilo`` and ``ilo5``
hardware types by using the ``network_data`` property
on the node field, operators can now apply network
configuration to be embedded in iLO based Virtual
Media based deployment ramdisks which include networking
configuration enabling the deployment to operate without
the use of DHCP.