summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorvmud213 <vinay50muddu@yahoo.com>2020-08-10 08:58:52 +0000
committervmud213 <vinay50muddu@yahoo.com>2020-09-17 13:20:53 +0000
commit6d36b0b785b4b009fbb3e05d4dc5524faa71d6ba (patch)
tree8ef2631c10ed25da698ceaede47fb7e35417cbf8
parentf1ea2ee6d1a7787cb86c77223283d6386379b68c (diff)
downloadironic-6d36b0b785b4b009fbb3e05d4dc5524faa71d6ba.tar.gz
Adds ilo-uefi-https boot interface to ilo5
Change-Id: I224eca4d8b331711369b17903098daa9fec27d7d Story: #2008073 Task: #40761
-rw-r--r--doc/source/admin/drivers/ilo.rst82
-rw-r--r--ironic/conf/ilo.py8
-rw-r--r--ironic/drivers/ilo.py6
-rw-r--r--ironic/drivers/modules/ilo/boot.py384
-rw-r--r--ironic/drivers/modules/ilo/common.py46
-rw-r--r--ironic/drivers/modules/image_utils.py30
-rw-r--r--ironic/tests/unit/drivers/modules/ilo/test_boot.py749
-rw-r--r--ironic/tests/unit/drivers/modules/ilo/test_common.py77
-rw-r--r--ironic/tests/unit/drivers/modules/test_image_utils.py93
-rw-r--r--releasenotes/notes/add-ilo-uefi-https-boot-interface-f3b163a8a6243283.yaml7
-rw-r--r--setup.cfg1
11 files changed, 1463 insertions, 20 deletions
diff --git a/doc/source/admin/drivers/ilo.rst b/doc/source/admin/drivers/ilo.rst
index 919a5064f..afe6df739 100644
--- a/doc/source/admin/drivers/ilo.rst
+++ b/doc/source/admin/drivers/ilo.rst
@@ -62,6 +62,7 @@ features:
* `Out of Band RAID Support`_
* `Out of Band Sanitize Disk Erase Support`_
* `Out of Band One Button Secure Erase Support`_
+* `UEFI-HTTPS Boot support`_
Hardware interfaces
^^^^^^^^^^^^^^^^^^^
@@ -191,7 +192,8 @@ The ``ilo`` hardware type supports following hardware interfaces:
The ``ilo5`` hardware type supports all the ``ilo`` interfaces described above,
-except for ``raid`` interface. The details of ``raid`` interface is as under:
+except for ``boot`` and ``raid`` interfaces. The details of ``boot`` and
+``raid`` interfaces is as under:
* raid
Supports ``ilo5`` and ``no-raid``. The default is ``ilo5``.
@@ -204,6 +206,19 @@ except for ``raid`` interface. The details of ``raid`` interface is as under:
enabled_hardware_types = ilo5
enabled_raid_interfaces = ilo5,no-raid
+* boot
+ Supports ``ilo-uefi-https`` apart from the other boot interfaces supported
+ by ``ilo`` hardware type.
+ This can be enabled by using the ``[DEFAULT]enabled_boot_interfaces``
+ option in ``ironic.conf`` as given below:
+
+ .. code-block:: ini
+
+ [DEFAULT]
+ enabled_hardware_types = ilo5
+ enabled_boot_interfaces = ilo-uefi-https,ilo-virtual-media
+
+
The ``ilo`` and ``ilo5`` hardware type support all standard ``deploy`` and
``network`` interface implementations, see :ref:`enable-hardware-interfaces`
@@ -290,6 +305,27 @@ Node configuration
This is optional property and is used when ``rescue`` interface is set to
``agent``.
+* The following properties are also required in node object's
+ ``driver_info`` if ``ilo-uefi-https`` boot interface is used for ``ilo5``
+ hardware type:
+
+ - ``ilo_deploy_kernel``: The glance UUID or a HTTPS URL of the deployment kernel.
+ - ``ilo_deploy_ramdisk``: The glance UUID or a HTTPS URL of the deployment ramdisk.
+ - ``ilo_bootloader``: The glance UUID or a HTTPS URL of the bootloader.
+ - ``ilo_rescue_kernel``: The glance UUID or a HTTPS URL of the rescue kernel.
+ This is optional property and is used when ``rescue`` interface is set to
+ ``agent``.
+ - ``ilo_rescue_ramdisk``: The glance UUID or a HTTP(S) URL of the rescue ramdisk.
+ This is optional property and is used when ``rescue`` interface is set to
+ ``agent``.
+
+ .. note::
+ ``ilo-uefi-https`` boot interface is supported by only ``ilo5`` hardware
+ type. If the images are not hosted in glance, the references
+ must be HTTPS URLs hosted by secure webserver. This boot interface can
+ be used only when the current boot mode is ``UEFI``.
+
+
* The following parameters are mandatory in ``driver_info``
if ``ilo-inspect`` inspect inteface is used and SNMPv3 inspection
(`SNMPv3 Authentication` in `HPE iLO4 User Guide`_) is desired:
@@ -438,7 +474,9 @@ the intermediate floppy image and the boot ISO.
.. note::
HTTPS is strongly recommended over HTTP web server configuration for security
enhancement. The ``ilo-virtual-media`` boot interface will send the instance's
- configdrive over an encrypted channel if web server is HTTPS enabled.
+ configdrive over an encrypted channel if web server is HTTPS enabled. However
+ for ``ilo-uefi-https`` boot interface HTTPS webserver is mandatory as this
+ interface only supports HTTPS URLs.
Enable driver
=============
@@ -2081,6 +2119,45 @@ Below are the steps to perform this clean step:
.. note::
Do not perform any iLO 5 configuration changes until this process is completed.
+UEFI-HTTPS Boot support
+^^^^^^^^^^^^^^^^^^^^^^^
+The UEFI firmware on Gen10 HPE Proliant servers supports booting from secured URLs.
+With this capability ``ilo5`` hardware with ``ilo-uefi-https`` boot interface supports
+deploy/rescue features in more secured environments.
+
+If swift is used as glance backend and ironic is configured to use swift to store
+temporary images, it is required that swift is configured on HTTPS so that the tempurl
+generated is HTTPS URL.
+
+If the webserver is used for hosting the temporary images, then the webserver is required
+to serve requests on HTTPS.
+
+If the images are hosted on a HTTPS webserver or swift configured with HTTPS with
+custom certificates, the user is required to export SSL certificates into iLO.
+Refer to `HPE Integrated Lights-Out Security Technology Brief`_ for more information.
+
+The following command can be used to enroll a ProLiant node with ``ilo5`` hardware type
+and ``ilo-uefi-https`` boot interface:
+
+.. code-block:: console
+
+ openstack baremetal node create \
+ --driver ilo5 \
+ --boot-interface ilo-uefi-https \
+ --deploy-interface direct \
+ --raid-interface ilo5 \
+ --rescue-interface agent \
+ --driver-info ilo_address=<ilo-ip-address> \
+ --driver-info ilo_username=<ilo-username> \
+ --driver-info ilo_password=<ilo-password> \
+ --driver-info ilo_deploy_kernel=<glance-uuid-of-deploy-kernel> \
+ --driver-info ilo_deploy_ramdisk=<glance-uuid-of-rescue-ramdisk> \
+ --driver-info ilo_bootloader=<glance-uuid-of-bootloader>
+
+.. note::
+ UEFI secure boot is not supported with ``ilo-uefi-https`` boot interface.
+
+
.. _`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
.. _`HPE iLO4 User Guide`: https://h20566.www2.hpe.com/hpsc/doc/public/display?docId=c03334051
@@ -2093,3 +2170,4 @@ Below are the steps to perform this clean step:
.. _`SUM`: https://h17007.www1.hpe.com/us/en/enterprise/servers/products/service_pack/hpsum/index.aspx
.. _`SUM User Guide`: https://h20565.www2.hpe.com/hpsc/doc/public/display?docId=c05210448
.. [1] `ironic-python-agent-builder`: https://docs.openstack.org/ironic-python-agent-builder/latest/install/index.html
+.. _`HPE Integrated Lights-Out Security Technology Brief`: http://h20564.www2.hpe.com/hpsc/doc/public/display?docId=c04530504
diff --git a/ironic/conf/ilo.py b/ironic/conf/ilo.py
index 8e5ca1d89..cb78edea6 100644
--- a/ironic/conf/ilo.py
+++ b/ironic/conf/ilo.py
@@ -103,6 +103,14 @@ opts = [
'"auto" for backward compatibility. When "auto" is '
'specified, default boot mode will be selected based '
'on boot mode settings on the system.')),
+ cfg.IntOpt('file_permission',
+ default=0o644,
+ help=_('File permission for swift-less image hosting with the '
+ 'octal permission representation of file access '
+ 'permissions. This setting defaults to ``644``, '
+ 'or as the octal number ``0o644`` in Python. '
+ 'This setting must be set to the octal number '
+ 'representation, meaning starting with ``0o``.')),
]
diff --git a/ironic/drivers/ilo.py b/ironic/drivers/ilo.py
index 4b824fffc..10676b411 100644
--- a/ironic/drivers/ilo.py
+++ b/ironic/drivers/ilo.py
@@ -78,6 +78,12 @@ class Ilo5Hardware(IloHardware):
"""
@property
+ def supported_boot_interfaces(self):
+ """List of supported boot interfaces."""
+ return super(Ilo5Hardware,
+ self).supported_boot_interfaces + [boot.IloUefiHttpsBoot]
+
+ @property
def supported_raid_interfaces(self):
"""List of supported raid interfaces."""
return [raid.Ilo5RAID] + super(
diff --git a/ironic/drivers/modules/ilo/boot.py b/ironic/drivers/modules/ilo/boot.py
index b6142bb8b..32ad16828 100644
--- a/ironic/drivers/modules/ilo/boot.py
+++ b/ironic/drivers/modules/ilo/boot.py
@@ -38,6 +38,7 @@ from ironic.drivers import base
from ironic.drivers.modules import boot_mode_utils
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.ilo import common as ilo_common
+from ironic.drivers.modules import image_utils
from ironic.drivers.modules import ipxe
from ironic.drivers.modules import pxe
@@ -56,6 +57,29 @@ RESCUE_PROPERTIES = {
"required if rescue mode is being used and ironic is "
"managing booting the rescue ramdisk.")
}
+REQUIRED_PROPERTIES_UEFI_HTTPS_BOOT = {
+ 'ilo_deploy_kernel': _("URL or Glance UUID of the deployment kernel. "
+ "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 '
+ 'value is required for rescue mode.'),
+ '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.'),
+ '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.")
+}
COMMON_PROPERTIES = REQUIRED_PROPERTIES
@@ -871,3 +895,363 @@ class IloiPXEBoot(ipxe.iPXEBoot):
# Volume boot in BIOS boot mode is handled using
# PXE boot interface
super(IloiPXEBoot, self).clean_up_instance(task)
+
+
+class IloUefiHttpsBoot(base.BootInterface):
+
+ capabilities = ['ramdisk_boot']
+
+ def get_properties(self):
+ """Return the properties of the interface.
+
+ :returns: dictionary of <property name>:<property description> entries.
+ """
+ return REQUIRED_PROPERTIES_UEFI_HTTPS_BOOT
+
+ def _validate_hrefs(self, image_dict):
+ """Validates if the given URLs are secured URLs.
+
+ If the given URLs are not glance images then validates if the URLs
+ are secured.
+
+ :param image_dict: a dictionary containing property/URL pair.
+ :returns: None
+ :raises: InvalidParameterValue, if any of URLs provided are insecure.
+ """
+ insecure_props = []
+
+ for prop in image_dict:
+ image_ref = image_dict.get(prop)
+ if not service_utils.is_glance_image(image_ref):
+ prefix = urlparse.urlparse(image_ref).scheme.lower()
+ if prefix == 'http':
+ insecure_props.append(prop)
+
+ if len(insecure_props) > 0:
+ error = (_('Secure URLs exposed over HTTPS are expected. '
+ 'Insecure URLs are provided for %s') % insecure_props)
+ raise exception.InvalidParameterValue(error)
+
+ def _parse_deploy_info(self, node):
+ """Gets the instance and driver specific Node deployment info.
+
+ This method validates whether the 'instance_info' and 'driver_info'
+ property of the supplied node contains the required information for
+ this driver to deploy images to the node.
+
+ :param node: a target node of the deployment
+ :returns: a dict with the instance_info and driver_info values.
+ :raises: MissingParameterValue, if any of the required parameters are
+ missing.
+ :raises: InvalidParameterValue, if any of the parameters have invalid
+ value.
+ """
+ deploy_info = {}
+ deploy_info.update(deploy_utils.get_image_instance_info(node))
+ deploy_info.update(self._parse_driver_info(node))
+
+ return deploy_info
+
+ def _parse_driver_info(self, node, mode='deploy'):
+ """Gets the node specific deploy/rescue info.
+
+ This method validates whether the 'driver_info' property of the
+ supplied node contains the required information for this driver to
+ deploy images to the node.
+
+ :param node: a single Node.
+ :param mode: Label indicating a deploy or rescue operation being
+ carried out on the node. Supported values are 'deploy' and
+ 'rescue'. Defaults to 'deploy', indicating deploy operation
+ is being carried out.
+ :returns: A dict with the driver_info values.
+ :raises: MissingParameterValue, if any of the required parameters are
+ missing.
+ """
+ info = node.driver_info
+
+ if mode == 'rescue':
+ params_to_check = RESCUE_PROPERTIES_UEFI_HTTPS_BOOT.keys()
+ else:
+ params_to_check = REQUIRED_PROPERTIES_UEFI_HTTPS_BOOT.keys()
+
+ deploy_info = {option: info.get(option)
+ for option in params_to_check}
+
+ self._validate_hrefs(deploy_info)
+
+ error_msg = (_("Error validating %s for iLO UEFI HTTPS boot. Some "
+ "parameters were missing in node's driver_info") % mode)
+ deploy_utils.check_for_missing_params(deploy_info, error_msg)
+
+ deploy_info.update(ilo_common.parse_driver_info(node))
+
+ return deploy_info
+
+ def _validate_driver_info(self, task):
+ """Validates the prerequisites for ilo-uefi-https boot interface.
+
+ This method validates whether the 'driver_info' property of the
+ supplied node contains the required information for this driver.
+
+ :param task: a TaskManager instance containing the node to act on.
+ :raises: InvalidParameterValue if any parameters are incorrect
+ :raises: MissingParameterValue if some mandatory information
+ is missing on the node
+ """
+ node = task.node
+
+ self._parse_driver_info(node)
+
+ def _validate_instance_image_info(self, task):
+ """Validate instance image information for the task's node.
+
+ :param task: a TaskManager instance containing the node to act on.
+ :raises: InvalidParameterValue, if some information is invalid.
+ :raises: MissingParameterValue if 'kernel_id' and 'ramdisk_id' are
+ missing in the Glance image or 'kernel' and 'ramdisk' not provided
+ in instance_info for non-Glance image.
+ """
+ node = task.node
+
+ d_info = deploy_utils.get_image_instance_info(node)
+
+ self._validate_hrefs(d_info)
+
+ if node.driver_internal_info.get('is_whole_disk_image'):
+ props = []
+ elif service_utils.is_glance_image(d_info['image_source']):
+ props = ['kernel_id', 'ramdisk_id']
+ else:
+ props = ['kernel', 'ramdisk']
+ deploy_utils.validate_image_properties(task.context, d_info, props)
+
+ @METRICS.timer('IloUefiHttpsBoot.validate')
+ def validate(self, task):
+ """Validate the deployment information for the task's node.
+
+ This method validates whether the 'driver_info' and/or 'instance_info'
+ properties of the task's node contains the required information for
+ this interface to function.
+
+ :param task: A TaskManager instance containing the node to act on.
+ :raises: InvalidParameterValue on malformed parameter(s)
+ :raises: MissingParameterValue on missing parameter(s)
+ """
+ node = task.node
+ boot_option = deploy_utils.get_boot_option(node)
+ try:
+ boot_mode = ilo_common.get_current_boot_mode(task.node)
+ except exception.IloOperationError:
+ error = _("Validation for 'ilo-uefi-https' boot interface failed. "
+ "Could not determine current boot mode for node "
+ "%(node)s.") % node.uuid
+ raise exception.InvalidParameterValue(error)
+
+ if boot_mode.lower() != 'uefi':
+ error = _("Validation for 'ilo-uefi-https' boot interface failed. "
+ "The node is required to be in 'UEFI' boot mode.")
+ raise exception.InvalidParameterValue(error)
+
+ boot_iso = node.instance_info.get('ilo_boot_iso')
+ if (boot_option == "ramdisk" and boot_iso):
+ if not service_utils.is_glance_image(boot_iso):
+ try:
+ image_service.HttpImageService().validate_href(boot_iso)
+ except exception.ImageRefValidationFailed:
+ with excutils.save_and_reraise_exception():
+ LOG.error("UEFI-HTTPS boot with 'ramdisk' "
+ "boot_option accepts only Glance images or "
+ "HTTPS URLs as "
+ "instance_info['ilo_boot_iso']. Either %s "
+ "is not a valid HTTPS URL or is not "
+ "reachable.", boot_iso)
+ return
+
+ self._validate_driver_info(task)
+
+ if task.driver.storage.should_write_image(task):
+ self._validate_instance_image_info(task)
+
+ def validate_inspection(self, task):
+ """Validate that the node has required properties for inspection.
+
+ :param task: A TaskManager instance with the node being checked
+ :raises: MissingParameterValue if node is missing one or more required
+ parameters
+ :raises: UnsupportedDriverExtension
+ """
+ try:
+ self._validate_driver_info(task)
+ except exception.MissingParameterValue:
+ # Fall back to non-managed in-band inspection
+ raise exception.UnsupportedDriverExtension(
+ driver=task.node.driver, extension='inspection')
+
+ @METRICS.timer('IloUefiHttpsBoot.prepare_ramdisk')
+ def prepare_ramdisk(self, task, ramdisk_params):
+ """Prepares the boot of deploy ramdisk using UEFI-HTTPS boot.
+
+ This method prepares the boot of the deploy or rescue ramdisk after
+ reading relevant information from the node's driver_info and
+ instance_info.
+
+ :param task: a task from TaskManager.
+ :param ramdisk_params: the parameters to be passed to the ramdisk.
+ :returns: None
+ :raises: MissingParameterValue, if some information is missing in
+ node's driver_info or instance_info.
+ :raises: InvalidParameterValue, if some information provided is
+ invalid.
+ :raises: IronicException, if some power or set boot boot device
+ operation failed on the node.
+ :raises: IloOperationError, if some operation on iLO failed.
+ """
+ node = task.node
+ # NOTE(TheJulia): If this method is being called by something
+ # aside from deployment, clean and rescue, such as conductor takeover,
+ # we should treat this as a no-op and move on otherwise we would
+ # modify the state of the node due to virtual media operations.
+ if node.provision_state not in (states.DEPLOYING,
+ states.CLEANING,
+ states.RESCUING,
+ states.INSPECTING):
+ return
+
+ prepare_node_for_deploy(task)
+
+ # Clear ilo_boot_iso if it's a glance image to force recreate
+ # another one again (or use existing one in glance).
+ # This is mainly for rebuild and rescue scenario.
+ if service_utils.is_glance_image(
+ node.instance_info.get('image_source')):
+ instance_info = node.instance_info
+ instance_info.pop('ilo_boot_iso', None)
+ node.instance_info = instance_info
+ node.save()
+
+ # NOTE(TheJulia): Since we're deploying, cleaning, or rescuing,
+ # with virtual media boot, we should generate a token!
+ manager_utils.add_secret_token(node, pregenerated=True)
+ ramdisk_params['ipa-agent-token'] = \
+ task.node.driver_internal_info['agent_secret_token']
+ task.node.save()
+
+ deploy_nic_mac = deploy_utils.get_single_nic_with_vif_port_id(task)
+ ramdisk_params['BOOTIF'] = deploy_nic_mac
+
+ mode = 'deploy'
+ if node.provision_state == states.RESCUING:
+ mode = 'rescue'
+
+ d_info = self._parse_driver_info(node, mode)
+
+ iso_ref = image_utils.prepare_deploy_iso(task, ramdisk_params,
+ mode, d_info)
+
+ LOG.debug("Set 'UEFIHTTP' as one time boot option on the node "
+ "%(node)s to boot from URL %(iso_ref)s.",
+ {'node': node.uuid, 'iso_ref': iso_ref})
+
+ ilo_common.setup_uefi_https(task, iso_ref)
+
+ @METRICS.timer('IloUefiHttpsBoot.clean_up_ramdisk')
+ def clean_up_ramdisk(self, task):
+ """Cleans up the boot of ironic ramdisk.
+
+ This method cleans up the environment that was setup for booting the
+ deploy ramdisk.
+
+ :param task: A task from TaskManager.
+ :returns: None
+ """
+ LOG.debug("Cleaning up deploy boot for "
+ "%(node)s", {'node': task.node.uuid})
+
+ image_utils.cleanup_iso_image(task)
+
+ @METRICS.timer('IloUefiHttpsBoot.prepare_instance')
+ def prepare_instance(self, task):
+ """Prepares the boot of instance.
+
+ This method prepares the boot of the instance after reading
+ relevant information from the node's instance_info.
+ 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.
+ - Otherwise it finds/creates the boot ISO, sets the node boot option
+ to UEFIHTTP and sets the URL as the boot ISO to boot the instance
+ image.
+
+ :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.
+ """
+ node = task.node
+ image_utils.cleanup_iso_image(task)
+ boot_option = deploy_utils.get_boot_option(task.node)
+
+ iwdi = node.driver_internal_info.get('is_whole_disk_image')
+ if boot_option == "local" or iwdi:
+ manager_utils.node_set_boot_device(task, boot_devices.DISK,
+ persistent=True)
+ LOG.debug("Node %(node)s is set to permanently boot from local "
+ "%(device)s", {'node': task.node.uuid,
+ 'device': boot_devices.DISK})
+ return
+
+ params = {}
+
+ if boot_option != 'ramdisk':
+ root_uuid = node.driver_internal_info.get('root_uuid_or_disk_id')
+ if not root_uuid and task.driver.storage.should_write_image(task):
+ LOG.warning(
+ "The UUID of the root partition could not be found for "
+ "node %s. Booting instance from disk anyway.", node.uuid)
+ manager_utils.node_set_boot_device(task, boot_devices.DISK,
+ persistent=True)
+
+ return
+ params.update(root_uuid=root_uuid)
+
+ d_info = self._parse_deploy_info(node)
+ iso_ref = image_utils.prepare_boot_iso(task, d_info, **params)
+
+ if boot_option != 'ramdisk':
+ i_info = node.instance_info
+ i_info['ilo_boot_iso'] = iso_ref
+ node.instance_info = i_info
+ node.save()
+
+ ilo_common.setup_uefi_https(task, iso_ref, persistent=True)
+
+ LOG.debug("Node %(node)s is set to boot from UEFIHTTP "
+ "boot option", {'node': task.node.uuid})
+
+ @METRICS.timer('IloUefiHttpsBoot.clean_up_instance')
+ def clean_up_instance(self, task):
+ """Cleans up the boot of instance.
+
+ This method cleans up the environment that was setup for booting
+ the instance.
+
+ :param task: A task from TaskManager.
+ :returns: None
+ """
+ LOG.debug("Cleaning up instance boot for "
+ "%(node)s", {'node': task.node.uuid})
+
+ image_utils.cleanup_iso_image(task)
+
+ @METRICS.timer('IloUefiHttpsBoot.validate_rescue')
+ def validate_rescue(self, task):
+ """Validate that the node has required properties for rescue.
+
+ :param task: a TaskManager instance with the node being checked
+ :raises: MissingParameterValue if node is missing one or more required
+ parameters
+ """
+ self._parse_driver_info(task.node, mode='rescue')
diff --git a/ironic/drivers/modules/ilo/common.py b/ironic/drivers/modules/ilo/common.py
index f297a6b3c..4ef8dc85e 100644
--- a/ironic/drivers/modules/ilo/common.py
+++ b/ironic/drivers/modules/ilo/common.py
@@ -922,3 +922,49 @@ def get_server_post_state(node):
except ilo_error.IloError as ilo_exception:
raise exception.IloOperationError(operation=operation,
error=ilo_exception)
+
+
+def setup_uefi_https(task, iso, persistent=False):
+ """Sets up system to boot from UEFIHTTP boot device.
+
+ Sets the one-time/persistent boot device to UEFIHTTP based
+ on the argument supplied.
+
+ :param task: a TaskManager instance containing the node to act on.
+ :param iso: ISO URL to be set to boot from.
+ :param persistent: Indicates whether the system should be set to boot
+ from the given device one-time or each time.
+ :raises: IloOperationError on an error from IloClient library.
+ :raises: IloOperationNotSupported if retrieving post state is not
+ supported on the server.
+ """
+ node = task.node
+ ilo_object = get_ilo_object(node)
+ scheme = urlparse.urlparse(iso).scheme.lower()
+
+ operation = (_("Setting up node %(node)s to boot from URL %(iso)s.") %
+ {'iso': iso, 'node': node.uuid})
+
+ if scheme != 'https':
+ msg = (_('Error setting up node %(node)s to boot from '
+ 'URL %(iso)s. A secure URL is expected that is exposed '
+ 'over HTTPS.') %
+ {'node': node.uuid, 'iso': iso})
+ raise exception.IloOperationNotSupported(operation=operation,
+ error=msg)
+
+ try:
+ ilo_object.set_http_boot_url(iso)
+ LOG.info("Set the node %(node)s to boot from URL %(iso)s "
+ "successfully.", {'node': node.uuid, 'iso': iso})
+ if not persistent:
+ ilo_object.set_one_time_boot('UEFIHTTP')
+ else:
+ ilo_object.update_persistent_boot(['UEFIHTTP'])
+
+ except ilo_error.IloCommandNotSupportedInBiosError as ilo_exception:
+ raise exception.IloOperationNotSupported(operation=operation,
+ error=ilo_exception)
+ except ilo_error.IloError as ilo_exception:
+ raise exception.IloOperationError(operation=operation,
+ error=ilo_exception)
diff --git a/ironic/drivers/modules/image_utils.py b/ironic/drivers/modules/image_utils.py
index f7a3a80d2..f341d6873 100644
--- a/ironic/drivers/modules/image_utils.py
+++ b/ironic/drivers/modules/image_utils.py
@@ -44,7 +44,14 @@ class ImageHandler(object):
"timeout": CONF.redfish.swift_object_expiry_timeout,
"image_subdir": "redfish",
"file_permission": CONF.redfish.file_permission
- }
+ },
+ "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
+ },
}
def __init__(self, driver):
@@ -380,6 +387,14 @@ def _prepare_iso_image(task, kernel_href, ramdisk_href,
return image_url
+def _find_param(param_str, param_dict):
+ val = None
+ for param_key in param_dict:
+ if param_str in param_key:
+ val = param_dict.get(param_key)
+ return val
+
+
def prepare_deploy_iso(task, params, mode, d_info):
"""Prepare deploy or rescue ISO image
@@ -406,9 +421,13 @@ def prepare_deploy_iso(task, params, mode, d_info):
:raises: ImageCreationFailed, if creating ISO image failed.
"""
- kernel_href = d_info.get('%s_kernel' % mode)
- ramdisk_href = d_info.get('%s_ramdisk' % mode)
- bootloader_href = d_info.get('bootloader')
+ kernel_str = '%s_kernel' % mode
+ ramdisk_str = '%s_ramdisk' % mode
+ bootloader_str = 'bootloader'
+
+ kernel_href = _find_param(kernel_str, d_info)
+ ramdisk_href = _find_param(ramdisk_str, d_info)
+ bootloader_href = _find_param(bootloader_str, d_info)
# TODO(TheJulia): At some point we should support something like
# boot_iso for the deploy interface, perhaps when we support config
@@ -494,7 +513,8 @@ def prepare_boot_iso(task, d_info, root_uuid=None):
"to generate boot ISO for %(node)s") %
{'node': task.node.uuid})
- bootloader_href = d_info.get('bootloader')
+ bootloader_str = 'bootloader'
+ bootloader_href = _find_param(bootloader_str, d_info)
return _prepare_iso_image(
task, kernel_href, ramdisk_href, bootloader_href,
diff --git a/ironic/tests/unit/drivers/modules/ilo/test_boot.py b/ironic/tests/unit/drivers/modules/ilo/test_boot.py
index f348dd52a..d681cb72c 100644
--- a/ironic/tests/unit/drivers/modules/ilo/test_boot.py
+++ b/ironic/tests/unit/drivers/modules/ilo/test_boot.py
@@ -18,6 +18,7 @@
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
@@ -36,14 +37,19 @@ 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 image_utils
from ironic.drivers.modules import ipxe
from ironic.drivers.modules import pxe
from ironic.drivers.modules.storage import noop as noop_storage
from ironic.drivers import utils as driver_utils
+from ironic.tests.unit.db import base as db_base
+from ironic.tests.unit.db import utils as db_utils
from ironic.tests.unit.drivers.modules.ilo import test_common
+from ironic.tests.unit.objects import utils as obj_utils
CONF = cfg.CONF
+INFO_DICT = db_utils.get_test_ilo_info()
class IloBootCommonMethodsTestCase(test_common.BaseIloTest):
@@ -1591,3 +1597,746 @@ class IloiPXEBootTestCase(test_common.BaseIloTest):
update_secure_boot_mode_mock.assert_called_once_with(task, True)
self.assertTrue(task.node.driver_internal_info.get(
'ilo_uefi_iscsi_boot'))
+
+
+class IloUefiHttpsBootTestCase(db_base.DbTestCase):
+ def setUp(self):
+ super(IloUefiHttpsBootTestCase, self).setUp()
+ self.driver = mock.Mock(boot=ilo_boot.IloUefiHttpsBoot())
+ n = {
+ 'driver': 'ilo5',
+ 'driver_info': INFO_DICT
+ }
+ self.config(enabled_hardware_types=['ilo5'],
+ enabled_boot_interfaces=['ilo-uefi-https'],
+ enabled_console_interfaces=['ilo'],
+ enabled_deploy_interfaces=['iscsi'],
+ enabled_inspect_interfaces=['ilo'],
+ enabled_management_interfaces=['ilo5'],
+ enabled_power_interfaces=['ilo'],
+ enabled_raid_interfaces=['ilo5'])
+ self.node = obj_utils.create_test_node(self.context, **n)
+
+ @mock.patch.object(urlparse, 'urlparse', spec_set=True,
+ autospec=True)
+ @mock.patch.object(service_utils, 'is_glance_image', spec_set=True,
+ autospec=True)
+ def test__validate_hrefs_https_image(self, is_glance_mock, urlparse_mock):
+ is_glance_mock.return_value = False
+ urlparse_mock.return_value.scheme = 'https'
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ data = {
+ 'ilo_deploy_kernel': 'https://a.b.c.d/kernel',
+ 'ilo_deploy_ramdisk': 'https://a.b.c.d/ramdisk',
+ 'ilo_bootloader': 'https://a.b.c.d/bootloader'
+ }
+ task.driver.boot._validate_hrefs(data)
+
+ glance_calls = [
+ mock.call('https://a.b.c.d/kernel'),
+ mock.call('https://a.b.c.d/ramdisk'),
+ mock.call('https://a.b.c.d/bootloader')
+ ]
+
+ is_glance_mock.assert_has_calls(glance_calls)
+ urlparse_mock.assert_has_calls(glance_calls)
+
+ @mock.patch.object(urlparse, 'urlparse', spec_set=True,
+ autospec=True)
+ @mock.patch.object(service_utils, 'is_glance_image', spec_set=True,
+ autospec=True)
+ def test__validate_hrefs_http_image(self, is_glance_mock, urlparse_mock):
+ is_glance_mock.return_value = False
+ scheme_mock = mock.PropertyMock(
+ side_effect=['http', 'https', 'http'])
+ type(urlparse_mock.return_value).scheme = scheme_mock
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ data = {
+ 'ilo_deploy_kernel': 'http://a.b.c.d/kernel',
+ 'ilo_deploy_ramdisk': 'https://a.b.c.d/ramdisk',
+ 'ilo_bootloader': 'http://a.b.c.d/bootloader'
+ }
+
+ glance_calls = [
+ mock.call('http://a.b.c.d/kernel'),
+ mock.call('https://a.b.c.d/ramdisk'),
+ mock.call('http://a.b.c.d/bootloader')
+ ]
+ self.assertRaisesRegex(exception.InvalidParameterValue,
+ "Secure URLs exposed over HTTPS are .*"
+ "['ilo_deploy_kernel', 'ilo_bootloader']",
+ task.driver.boot._validate_hrefs, data)
+ is_glance_mock.assert_has_calls(glance_calls)
+ urlparse_mock.assert_has_calls(glance_calls)
+
+ @mock.patch.object(urlparse, 'urlparse', spec_set=True,
+ autospec=True)
+ @mock.patch.object(service_utils, 'is_glance_image', spec_set=True,
+ autospec=True)
+ def test__validate_hrefs_glance_image(self, is_glance_mock, urlparse_mock):
+ is_glance_mock.return_value = True
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ data = {
+ 'ilo_deploy_kernel': 'https://a.b.c.d/kernel',
+ 'ilo_deploy_ramdisk': 'https://a.b.c.d/ramdisk',
+ 'ilo_bootloader': 'https://a.b.c.d/bootloader'
+ }
+
+ task.driver.boot._validate_hrefs(data)
+
+ glance_calls = [
+ mock.call('https://a.b.c.d/kernel'),
+ mock.call('https://a.b.c.d/ramdisk'),
+ mock.call('https://a.b.c.d/bootloader')
+ ]
+
+ is_glance_mock.assert_has_calls(glance_calls)
+ urlparse_mock.assert_not_called()
+
+ @mock.patch.object(ilo_boot.IloUefiHttpsBoot, '_parse_driver_info',
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'get_image_instance_info',
+ autospec=True)
+ def test__parse_deploy_info(self, get_img_inst_mock,
+ parse_driver_mock):
+ parse_driver_mock.return_value = {
+ 'ilo_deploy_kernel': 'deploy-kernel',
+ 'ilo_deploy_ramdisk': 'deploy-ramdisk',
+ 'ilo_bootloader': 'bootloader'
+ }
+ get_img_inst_mock.return_value = {
+ 'ilo_boot_iso': 'boot-iso',
+ 'image_source': '6b2f0c0c-79e8-4db6-842e-43c9764204af'
+ }
+ instance_info = self.node.instance_info
+ driver_info = self.node.driver_info
+
+ instance_info['ilo_boot_iso'] = 'boot-iso'
+ instance_info['image_source'] = '6b2f0c0c-79e8-4db6-842e-43c9764204af'
+ self.node.instance_info = instance_info
+
+ driver_info['ilo_deploy_kernel'] = 'deploy-kernel'
+ driver_info['ilo_deploy_ramdisk'] = 'deploy-ramdisk'
+ driver_info['ilo_bootloader'] = 'bootloader'
+ self.node.driver_info = driver_info
+ self.node.save()
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ expected_info = {
+ 'ilo_deploy_kernel': 'deploy-kernel',
+ 'ilo_deploy_ramdisk': 'deploy-ramdisk',
+ 'ilo_bootloader': 'bootloader',
+ 'ilo_boot_iso': 'boot-iso',
+ 'image_source': '6b2f0c0c-79e8-4db6-842e-43c9764204af'
+ }
+
+ actual_info = task.driver.boot._parse_deploy_info(task.node)
+ get_img_inst_mock.assert_called_once_with(task.node)
+ parse_driver_mock.assert_called_once_with(mock.ANY, task.node)
+ self.assertEqual(expected_info, actual_info)
+
+ @mock.patch.object(ilo_boot.IloUefiHttpsBoot, '_validate_hrefs',
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'check_for_missing_params',
+ autospec=True)
+ @mock.patch.object(ilo_common, 'parse_driver_info', autospec=True)
+ def test__parse_driver_info_default_mode(
+ self, parse_driver_mock, check_missing_mock, validate_href_mock):
+ parse_driver_mock.return_value = {
+ 'ilo_username': 'admin',
+ 'ilo_password': 'admin'
+ }
+ driver_info = self.node.driver_info
+ driver_info['ilo_deploy_kernel'] = 'deploy-kernel'
+ driver_info['ilo_rescue_kernel'] = 'rescue-kernel'
+ driver_info['ilo_deploy_ramdisk'] = 'deploy-ramdisk'
+ driver_info['ilo_rescue_ramdisk'] = 'rescue-ramdisk'
+ driver_info['ilo_bootloader'] = 'bootloader'
+ driver_info['dummy_key'] = 'dummy-value'
+ self.node.driver_info = driver_info
+ self.node.save()
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ deploy_info = {
+ 'ilo_deploy_kernel': 'deploy-kernel',
+ 'ilo_deploy_ramdisk': 'deploy-ramdisk',
+ 'ilo_bootloader': 'bootloader'
+ }
+ actual_info = deploy_info
+ actual_info.update({'ilo_username': 'admin',
+ 'ilo_password': 'admin'})
+
+ expected_info = task.driver.boot._parse_driver_info(task.node)
+ validate_href_mock.assert_called_once_with(mock.ANY, deploy_info)
+ check_missing_mock.assert_called_once_with(deploy_info, mock.ANY)
+ parse_driver_mock.assert_called_once_with(task.node)
+ self.assertEqual(actual_info, expected_info)
+
+ @mock.patch.object(ilo_boot.IloUefiHttpsBoot, '_validate_hrefs',
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'check_for_missing_params',
+ autospec=True)
+ @mock.patch.object(ilo_common, 'parse_driver_info', autospec=True)
+ def test__parse_driver_info_rescue_mode(
+ self, parse_driver_mock, check_missing_mock, validate_href_mock):
+ parse_driver_mock.return_value = {
+ 'ilo_username': 'admin',
+ 'ilo_password': 'admin'
+ }
+ mode = 'rescue'
+ driver_info = self.node.driver_info
+ driver_info['ilo_deploy_kernel'] = 'deploy-kernel'
+ driver_info['ilo_rescue_kernel'] = 'rescue-kernel'
+ driver_info['ilo_deploy_ramdisk'] = 'deploy-ramdisk'
+ driver_info['ilo_rescue_ramdisk'] = 'rescue-ramdisk'
+ driver_info['ilo_bootloader'] = 'bootloader'
+ driver_info['dummy_key'] = 'dummy-value'
+ self.node.driver_info = driver_info
+ self.node.save()
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ deploy_info = {
+ 'ilo_rescue_kernel': 'rescue-kernel',
+ 'ilo_rescue_ramdisk': 'rescue-ramdisk',
+ 'ilo_bootloader': 'bootloader'
+ }
+ actual_info = deploy_info
+ actual_info.update({'ilo_username': 'admin',
+ 'ilo_password': 'admin'})
+
+ expected_info = task.driver.boot._parse_driver_info(
+ task.node, mode)
+ check_missing_mock.assert_called_once_with(deploy_info, mock.ANY)
+ validate_href_mock.assert_called_once_with(mock.ANY, deploy_info)
+ parse_driver_mock.assert_called_once_with(task.node)
+ self.assertEqual(actual_info, expected_info)
+
+ @mock.patch.object(ilo_boot.IloUefiHttpsBoot, '_validate_hrefs',
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'validate_image_properties',
+ spec_set=True, autospec=True)
+ @mock.patch.object(deploy_utils, 'get_image_instance_info',
+ spec_set=True, autospec=True)
+ @mock.patch.object(service_utils, 'is_glance_image', spec_set=True,
+ autospec=True)
+ def test__validate_instance_image_info_not_iwdi(
+ self, glance_mock, get_image_inst_mock, validate_image_mock,
+ validate_href_mock):
+ instance_info = {
+ 'ilo_boot_iso': 'boot-iso',
+ 'image_source': '6b2f0c0c-79e8-4db6-842e-43c9764204af'
+ }
+ driver_internal_info = self.node.driver_internal_info
+ driver_internal_info.pop('is_whole_disk_image', None)
+ self.node.driver_internal_info = driver_internal_info
+ self.node.instance_info = instance_info
+ self.node.save()
+ get_image_inst_mock.return_value = instance_info
+ glance_mock.return_value = True
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+
+ task.driver.boot._validate_instance_image_info(task)
+ get_image_inst_mock.assert_called_once_with(task.node)
+ glance_mock.assert_called_once_with(
+ '6b2f0c0c-79e8-4db6-842e-43c9764204af')
+ validate_image_mock.assert_called_once_with(task.context,
+ instance_info,
+ ['kernel_id',
+ 'ramdisk_id'])
+ validate_href_mock.assert_called_once_with(mock.ANY, instance_info)
+
+ @mock.patch.object(ilo_boot.IloUefiHttpsBoot, '_validate_hrefs',
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'validate_image_properties',
+ spec_set=True, autospec=True)
+ @mock.patch.object(deploy_utils, 'get_image_instance_info',
+ spec_set=True, autospec=True)
+ @mock.patch.object(service_utils, 'is_glance_image', spec_set=True,
+ autospec=True)
+ def test__validate_instance_image_info_neither_iwdi_nor_glance(
+ self, glance_mock, get_image_inst_mock, validate_image_mock,
+ validate_href_mock):
+
+ instance_info = {
+ 'ilo_boot_iso': 'boot-iso',
+ 'image_source': '6b2f0c0c-79e8-4db6-842e-43c9764204af'
+ }
+ driver_internal_info = self.node.driver_internal_info
+ driver_internal_info.pop('is_whole_disk_image', None)
+ self.node.driver_internal_info = driver_internal_info
+ self.node.instance_info = instance_info
+ self.node.save()
+ get_image_inst_mock.return_value = instance_info
+ glance_mock.return_value = False
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+
+ task.driver.boot._validate_instance_image_info(task)
+ get_image_inst_mock.assert_called_once_with(task.node)
+ glance_mock.assert_called_once_with(
+ '6b2f0c0c-79e8-4db6-842e-43c9764204af')
+ validate_image_mock.assert_called_once_with(task.context,
+ instance_info,
+ ['kernel',
+ 'ramdisk'])
+ validate_href_mock.assert_called_once_with(mock.ANY, instance_info)
+
+ @mock.patch.object(ilo_boot.IloUefiHttpsBoot, '_validate_hrefs',
+ autospec=True)
+ @mock.patch.object(deploy_utils, 'validate_image_properties',
+ spec_set=True, autospec=True)
+ @mock.patch.object(deploy_utils, 'get_image_instance_info',
+ spec_set=True, autospec=True)
+ @mock.patch.object(service_utils, 'is_glance_image', spec_set=True,
+ autospec=True)
+ def test__validate_instance_image_info_iwdi(
+ self, glance_mock, get_image_inst_mock, validate_image_mock,
+ validate_href_mock):
+ instance_info = {
+ 'ilo_boot_iso': 'boot-iso',
+ 'image_source': '6b2f0c0c-79e8-4db6-842e-43c9764204af'
+ }
+ driver_internal_info = self.node.driver_internal_info or {}
+ driver_internal_info['is_whole_disk_image'] = True
+ self.node.driver_internal_info = driver_internal_info
+ self.node.save()
+ get_image_inst_mock.return_value = instance_info
+ glance_mock.return_value = True
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+
+ task.driver.boot._validate_instance_image_info(task)
+ get_image_inst_mock.assert_called_once_with(task.node)
+ glance_mock.assert_not_called()
+ validate_image_mock.assert_called_once_with(task.context,
+ instance_info, [])
+ validate_href_mock.assert_called_once_with(mock.ANY, instance_info)
+
+ @mock.patch.object(ilo_common, 'get_current_boot_mode',
+ autospec=True)
+ @mock.patch.object(noop_storage.NoopStorage, 'should_write_image',
+ autospec=True)
+ @mock.patch.object(ilo_boot.IloUefiHttpsBoot, '_validate_driver_info',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_boot.IloUefiHttpsBoot,
+ '_validate_instance_image_info',
+ spec_set=True, autospec=True)
+ def test_validate(self, mock_val_instance_image_info,
+ mock_val_driver_info, storage_mock, get_boot_mock):
+ get_boot_mock.return_value = 'UEFI'
+ instance_info = self.node.instance_info
+
+ instance_info['ilo_boot_iso'] = 'boot-iso'
+ instance_info['image_source'] = '6b2f0c0c-79e8-4db6-842e-43c9764204af'
+ self.node.instance_info = instance_info
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+
+ task.node.driver_info['ilo_deploy_kernel'] = 'deploy-kernel'
+ task.node.driver_info['ilo_deploy_ramdisk'] = 'deploy-ramdisk'
+ task.node.driver_info['ilo_bootloader'] = 'bootloader'
+ storage_mock.return_value = True
+ task.driver.boot.validate(task)
+ mock_val_instance_image_info.assert_called_once_with(
+ mock.ANY, task)
+ mock_val_driver_info.assert_called_once_with(mock.ANY, task)
+
+ @mock.patch.object(ilo_common, 'get_current_boot_mode',
+ autospec=True)
+ @mock.patch.object(noop_storage.NoopStorage, 'should_write_image',
+ autospec=True)
+ @mock.patch.object(ilo_boot.IloUefiHttpsBoot, '_validate_driver_info',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_boot.IloUefiHttpsBoot,
+ '_validate_instance_image_info',
+ spec_set=True, autospec=True)
+ def test_validate_bios(self, mock_val_instance_image_info,
+ mock_val_driver_info, storage_mock, get_boot_mock):
+ get_boot_mock.return_value = 'LEGACY'
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaisesRegex(exception.InvalidParameterValue,
+ "Validation for 'ilo-uefi-https' boot "
+ "interface failed.*",
+ task.driver.boot.validate, task)
+ mock_val_instance_image_info.assert_not_called()
+ mock_val_driver_info.assert_not_called()
+
+ @mock.patch.object(ilo_common, 'get_current_boot_mode',
+ autospec=True)
+ @mock.patch.object(ilo_boot.IloUefiHttpsBoot, '_validate_driver_info',
+ spec_set=True, autospec=True)
+ @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)
+ def test_validate_ramdisk_boot_option_glance(self, is_glance_image_mock,
+ validate_href_mock,
+ val_driver_info_mock,
+ get_boot_mock):
+ get_boot_mock.return_value = 'UEFI'
+ instance_info = self.node.instance_info
+ boot_iso = '6b2f0c0c-79e8-4db6-842e-43c9764204af'
+ instance_info['ilo_boot_iso'] = boot_iso
+ instance_info['capabilities'] = '{"boot_option": "ramdisk"}'
+ self.node.instance_info = instance_info
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ is_glance_image_mock.return_value = True
+ task.driver.boot.validate(task)
+ is_glance_image_mock.assert_called_once_with(boot_iso)
+ self.assertFalse(validate_href_mock.called)
+ self.assertFalse(val_driver_info_mock.called)
+
+ @mock.patch.object(ilo_common, 'get_current_boot_mode',
+ autospec=True)
+ @mock.patch.object(ilo_boot.IloUefiHttpsBoot, '_validate_driver_info',
+ spec_set=True, autospec=True)
+ @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)
+ def test_validate_ramdisk_boot_option_webserver(self, is_glance_image_mock,
+ validate_href_mock,
+ val_driver_info_mock,
+ get_boot_mock):
+ get_boot_mock.return_value = 'UEFI'
+ instance_info = self.node.instance_info
+ boot_iso = 'http://myserver/boot.iso'
+ instance_info['ilo_boot_iso'] = boot_iso
+ instance_info['capabilities'] = '{"boot_option": "ramdisk"}'
+ self.node.instance_info = instance_info
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ is_glance_image_mock.return_value = False
+ task.driver.boot.validate(task)
+ is_glance_image_mock.assert_called_once_with(boot_iso)
+ validate_href_mock.assert_called_once_with(mock.ANY, boot_iso)
+ self.assertFalse(val_driver_info_mock.called)
+
+ @mock.patch.object(ilo_common, 'get_current_boot_mode',
+ autospec=True)
+ @mock.patch.object(ilo_boot.LOG, 'error', spec_set=True, autospec=True)
+ @mock.patch.object(ilo_boot.IloUefiHttpsBoot, '_validate_driver_info',
+ spec_set=True, autospec=True)
+ @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)
+ def test_validate_ramdisk_boot_option_webserver_exc(
+ self, is_glance_image_mock, validate_href_mock,
+ val_driver_info_mock, log_mock, get_boot_mock):
+
+ get_boot_mock.return_value = 'UEFI'
+ instance_info = self.node.instance_info
+ validate_href_mock.side_effect = exception.ImageRefValidationFailed(
+ image_href='http://myserver/boot.iso', reason='fail')
+ boot_iso = 'http://myserver/boot.iso'
+ instance_info['ilo_boot_iso'] = boot_iso
+ instance_info['capabilities'] = '{"boot_option": "ramdisk"}'
+ self.node.instance_info = instance_info
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+
+ is_glance_image_mock.return_value = False
+ self.assertRaisesRegex(exception.ImageRefValidationFailed,
+ "Validation of image href "
+ "http://myserver/boot.iso failed",
+ task.driver.boot.validate, task)
+ is_glance_image_mock.assert_called_once_with(boot_iso)
+ validate_href_mock.assert_called_once_with(mock.ANY, boot_iso)
+ self.assertFalse(val_driver_info_mock.called)
+ self.assertIn("UEFI-HTTPS boot with 'ramdisk' boot_option "
+ "accepts only Glance images or HTTPS URLs as "
+ "instance_info['ilo_boot_iso'].",
+ log_mock.call_args[0][0])
+
+ @mock.patch.object(ilo_boot.IloUefiHttpsBoot, '_validate_driver_info',
+ autospec=True)
+ def test_validate_inspection(self, mock_val_driver_info):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.driver.boot.validate_inspection(task)
+ mock_val_driver_info.assert_called_once_with(mock.ANY, task)
+
+ @mock.patch.object(ilo_boot.IloUefiHttpsBoot, '_parse_driver_info',
+ spec_set=True, autospec=True)
+ def test_validate_inspection_missing(self, mock_parse_driver_info):
+ mock_parse_driver_info.side_effect = exception.MissingParameterValue(
+ "Error validating iLO UEFIHTTPS for deploy.")
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ self.assertRaises(exception.UnsupportedDriverExtension,
+ task.driver.boot.validate_inspection, task)
+
+ @mock.patch.object(ilo_common, 'setup_uefi_https',
+ spec_set=True, autospec=True)
+ @mock.patch.object(image_utils, 'prepare_deploy_iso',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_boot, 'prepare_node_for_deploy',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_boot.IloUefiHttpsBoot, '_parse_driver_info',
+ spec_set=True, autospec=True)
+ @mock.patch.object(deploy_utils, 'get_single_nic_with_vif_port_id',
+ spec_set=True, autospec=True)
+ def _test_prepare_ramdisk(self, get_nic_mock,
+ parse_driver_mock,
+ prepare_node_for_deploy_mock,
+ prepare_deploy_iso_mock,
+ setup_uefi_https_mock,
+ ilo_boot_iso, image_source,
+ ramdisk_params={'a': 'b'},
+ mode='deploy', state=states.DEPLOYING):
+ self.node.provision_state = state
+ self.node.save()
+ instance_info = self.node.instance_info
+ instance_info['ilo_boot_iso'] = ilo_boot_iso
+ instance_info['image_source'] = image_source
+ self.node.instance_info = instance_info
+ self.node.save()
+ iso = 'provisioning-iso'
+
+ d_info = {
+ 'ilo_' + mode + '_kernel': mode + '-kernel',
+ 'ilo_' + mode + '_ramdisk': mode + '-ramdisk',
+ 'ilo_' + 'bootloader': 'bootloader'
+ }
+ parse_driver_mock.return_value = d_info
+ prepare_deploy_iso_mock.return_value = 'recreated-iso'
+
+ get_nic_mock.return_value = '12:34:56:78:90:ab'
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+
+ driver_info = task.node.driver_info
+ driver_info['ilo_%s_iso' % mode] = iso
+ task.node.driver_info = driver_info
+
+ task.driver.boot.prepare_ramdisk(task, ramdisk_params)
+
+ prepare_node_for_deploy_mock.assert_called_once_with(task)
+
+ get_nic_mock.assert_called_once_with(task)
+ parse_driver_mock.assert_called_once_with(
+ mock.ANY, task.node, mode)
+ prepare_deploy_iso_mock.assert_called_once_with(
+ task, ramdisk_params, mode, d_info)
+ setup_uefi_https_mock.assert_called_once_with(task,
+ 'recreated-iso')
+
+ def test_prepare_ramdisk_rescue_glance_image(self):
+ self._test_prepare_ramdisk(
+ ilo_boot_iso='swift:abcdef',
+ image_source='6b2f0c0c-79e8-4db6-842e-43c9764204af',
+ mode='rescue', state=states.RESCUING)
+ self.node.refresh()
+ self.assertNotIn('ilo_boot_iso', self.node.instance_info)
+
+ def test_prepare_ramdisk_rescue_not_a_glance_image(self):
+ self._test_prepare_ramdisk(
+ ilo_boot_iso='http://mybootiso',
+ image_source='http://myimage',
+ mode='rescue', state=states.RESCUING)
+ self.node.refresh()
+ self.assertEqual('http://mybootiso',
+ self.node.instance_info['ilo_boot_iso'])
+
+ def test_prepare_ramdisk_glance_image(self):
+ self._test_prepare_ramdisk(
+ ilo_boot_iso='swift:abcdef',
+ image_source='6b2f0c0c-79e8-4db6-842e-43c9764204af')
+ self.node.refresh()
+ self.assertNotIn('ilo_boot_iso', self.node.instance_info)
+
+ def test_prepare_ramdisk_not_a_glance_image(self):
+ self._test_prepare_ramdisk(
+ ilo_boot_iso='http://mybootiso',
+ image_source='http://myimage')
+ self.node.refresh()
+ self.assertEqual('http://mybootiso',
+ self.node.instance_info['ilo_boot_iso'])
+
+ def test_prepare_ramdisk_glance_image_cleaning(self):
+ self._test_prepare_ramdisk(
+ ilo_boot_iso='swift:abcdef',
+ image_source='6b2f0c0c-79e8-4db6-842e-43c9764204af',
+ mode='deploy', state=states.CLEANING)
+ self.node.refresh()
+ self.assertNotIn('ilo_boot_iso', self.node.instance_info)
+
+ def test_prepare_ramdisk_not_a_glance_image_cleaning(self):
+ self._test_prepare_ramdisk(
+ ilo_boot_iso='http://mybootiso',
+ image_source='http://myimage',
+ mode='deploy', state=states.CLEANING)
+ self.node.refresh()
+ self.assertEqual('http://mybootiso',
+ self.node.instance_info['ilo_boot_iso'])
+
+ @mock.patch.object(image_utils, 'cleanup_iso_image', spec_set=True,
+ autospec=True)
+ def test_clean_up_ramdisk(self, cleanup_iso_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.boot.clean_up_ramdisk(task)
+ cleanup_iso_mock.assert_called_once_with(task)
+
+ @mock.patch.object(image_utils, 'cleanup_iso_image', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'setup_uefi_https',
+ spec_set=True, autospec=True)
+ @mock.patch.object(image_utils, 'prepare_deploy_iso',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_boot.IloUefiHttpsBoot, '_parse_deploy_info',
+ spec_set=True, autospec=True)
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ def _test_prepare_instance_local_or_whole_disk_image(
+ self, set_boot_device_mock,
+ parse_deploy_mock, prepare_iso_mock, setup_uefi_https_mock,
+ cleanup_iso_mock):
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.boot.prepare_instance(task)
+
+ set_boot_device_mock.assert_called_once_with(task,
+ boot_devices.DISK,
+ persistent=True)
+ cleanup_iso_mock.assert_called_once_with(task)
+ prepare_iso_mock.assert_not_called()
+ setup_uefi_https_mock.assert_not_called()
+
+ def test_prepare_instance_image_local(self):
+ self.node.instance_info = {'capabilities': '{"boot_option": "local"}'}
+ self.node.save()
+ self._test_prepare_instance_local_or_whole_disk_image()
+
+ def test_prepare_instance_whole_disk_image(self):
+ self.node.driver_internal_info = {'is_whole_disk_image': True}
+ self.node.save()
+ self._test_prepare_instance_local_or_whole_disk_image()
+
+ @mock.patch.object(image_utils, 'cleanup_iso_image', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'setup_uefi_https',
+ spec_set=True, autospec=True)
+ @mock.patch.object(image_utils, 'prepare_boot_iso',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_boot.IloUefiHttpsBoot, '_parse_deploy_info',
+ spec_set=True, autospec=True)
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ def test_prepare_instance_partition_image(
+ self, set_boot_device_mock,
+ parse_deploy_mock, prepare_iso_mock, setup_uefi_https_mock,
+ cleanup_iso_mock):
+
+ self.node.instance_info = {
+ 'capabilities': '{"boot_option": "netboot"}'
+ }
+ self.node.driver_internal_info = {
+ 'root_uuid_or_disk_id': (
+ "12312642-09d3-467f-8e09-12385826a123")
+ }
+ self.node.driver_internal_info.update({'is_whole_disk_image': False})
+ self.node.save()
+ d_info = {'a': 'x', 'b': 'y'}
+ parse_deploy_mock.return_value = d_info
+ prepare_iso_mock.return_value = "recreated-iso"
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.boot.prepare_instance(task)
+
+ cleanup_iso_mock.assert_called_once_with(task)
+ set_boot_device_mock.assert_not_called()
+ parse_deploy_mock.assert_called_once_with(mock.ANY, task.node)
+ prepare_iso_mock.assert_called_once_with(
+ task, d_info, root_uuid='12312642-09d3-467f-8e09-12385826a123')
+ setup_uefi_https_mock.assert_called_once_with(
+ task, "recreated-iso", True)
+ self.assertEqual(task.node.instance_info['ilo_boot_iso'],
+ "recreated-iso")
+
+ @mock.patch.object(image_utils, 'cleanup_iso_image', spec_set=True,
+ autospec=True)
+ @mock.patch.object(ilo_common, 'setup_uefi_https',
+ spec_set=True, autospec=True)
+ @mock.patch.object(image_utils, 'prepare_boot_iso',
+ spec_set=True, autospec=True)
+ @mock.patch.object(ilo_boot.IloUefiHttpsBoot, '_parse_deploy_info',
+ spec_set=True, autospec=True)
+ @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True,
+ autospec=True)
+ def test_prepare_instance_boot_ramdisk(
+ self, set_boot_device_mock,
+ parse_deploy_mock, prepare_iso_mock, setup_uefi_https_mock,
+ cleanup_iso_mock):
+
+ self.node.driver_internal_info.update({'is_whole_disk_image': False})
+ self.node.save()
+ d_info = {'a': 'x', 'b': 'y'}
+ parse_deploy_mock.return_value = d_info
+ prepare_iso_mock.return_value = "recreated-iso"
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ instance_info = task.node.instance_info
+ instance_info['capabilities'] = '{"boot_option": "ramdisk"}'
+ task.node.instance_info = instance_info
+ task.node.save()
+ task.driver.boot.prepare_instance(task)
+
+ cleanup_iso_mock.assert_called_once_with(task)
+ set_boot_device_mock.assert_not_called()
+ parse_deploy_mock.assert_called_once_with(mock.ANY, task.node)
+ prepare_iso_mock.assert_called_once_with(
+ task, d_info)
+ setup_uefi_https_mock.assert_called_once_with(
+ task, "recreated-iso", True)
+ self.assertTrue('ilo_boot_iso' not in task.node.instance_info)
+
+ @mock.patch.object(image_utils, 'cleanup_iso_image', spec_set=True,
+ autospec=True)
+ def test_clean_up_instance(self, cleanup_iso_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.driver.boot.clean_up_instance(task)
+ cleanup_iso_mock.assert_called_once_with(task)
+
+ def test_validate_rescue(self):
+ driver_info = self.node.driver_info
+ driver_info['ilo_rescue_kernel'] = 'rescue-kernel'
+ driver_info['ilo_rescue_ramdisk'] = 'rescue-ramdisk'
+ driver_info['ilo_bootloader'] = 'bootloader'
+ self.node.driver_info = driver_info
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.driver.boot.validate_rescue(task)
+
+ def test_validate_rescue_no_rescue_ramdisk(self):
+ driver_info = self.node.driver_info
+ driver_info['ilo_rescue_kernel'] = 'rescue-kernel'
+ driver_info['ilo_rescue_ramdisk'] = 'rescue-ramdisk'
+ driver_info.pop('ilo_bootloader', None)
+ self.node.driver_info = driver_info
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ self.assertRaisesRegex(exception.MissingParameterValue,
+ "Error validating rescue for iLO UEFI "
+ "HTTPS boot.* ['ilo_bootloader']",
+ task.driver.boot.validate_rescue, task)
diff --git a/ironic/tests/unit/drivers/modules/ilo/test_common.py b/ironic/tests/unit/drivers/modules/ilo/test_common.py
index c743a6260..2877e6195 100644
--- a/ironic/tests/unit/drivers/modules/ilo/test_common.py
+++ b/ironic/tests/unit/drivers/modules/ilo/test_common.py
@@ -675,7 +675,6 @@ class IloCommonMethodsTestCase(BaseIloTest):
swift_obj_mock = swift_api_mock.return_value
boot_iso = 'swift:object-name'
swift_obj_mock.get_temp_url.return_value = 'image_url'
- CONF.keystone_authtoken.auth_uri = 'http://authurl'
CONF.ilo.swift_ilo_container = 'ilo_cont'
CONF.ilo.swift_object_expiry_timeout = 1
with task_manager.acquire(self.context, self.node.uuid,
@@ -1206,3 +1205,79 @@ class IloCommonMethodsTestCase(BaseIloTest):
ilo_common.get_server_post_state,
task.node)
ilo_mock_object.get_host_post_state.assert_called_once_with()
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_setup_uefi_https_scheme_http(self, get_ilo_object_mock):
+ ilo_mock_object = get_ilo_object_mock.return_value
+ iso = "http://1.1.1.1/image.iso"
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.IloOperationNotSupported,
+ ilo_common.setup_uefi_https,
+ task, iso, True)
+
+ ilo_mock_object.set_http_boot_url.assert_not_called()
+ ilo_mock_object.set_one_time_boot.assert_not_called()
+ ilo_mock_object.update_persistent_boot.assert_not_called()
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def _test_setup_uefi_https(self, get_ilo_object_mock, persistent):
+
+ ilo_mock_object = get_ilo_object_mock.return_value
+
+ iso = "https://1.1.1.1/image.iso"
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ ilo_common.setup_uefi_https(task, iso, persistent=persistent)
+
+ ilo_mock_object.set_http_boot_url.assert_called_once_with(iso)
+ if persistent:
+ ilo_mock_object.set_one_time_boot.assert_not_called()
+ ilo_mock_object.update_persistent_boot.assert_called_once_with(
+ ['UEFIHTTP'])
+ else:
+ ilo_mock_object.set_one_time_boot.assert_called_once_with(
+ 'UEFIHTTP')
+ ilo_mock_object.update_persistent_boot.assert_not_called()
+
+ def test_setup_uefi_https_persistent_true(self):
+ self._test_setup_uefi_https(persistent=True)
+
+ def test_setup_uefi_https_persistent_false(self):
+ self._test_setup_uefi_https(persistent=False)
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_setup_uefi_https_raises_not_supported(self, get_ilo_object_mock):
+ ilo_mock_object = get_ilo_object_mock.return_value
+
+ exc = ilo_error.IloCommandNotSupportedInBiosError('error')
+ ilo_mock_object.set_http_boot_url.side_effect = exc
+
+ iso = "https://1.1.1.1/image.iso"
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.IloOperationNotSupported,
+ ilo_common.setup_uefi_https,
+ task, iso, True)
+
+ @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True,
+ autospec=True)
+ def test_setup_uefi_https_raises_ilo_error(self, get_ilo_object_mock):
+ ilo_mock_object = get_ilo_object_mock.return_value
+
+ exc = ilo_error.IloError('error')
+ ilo_mock_object.set_http_boot_url.side_effect = exc
+
+ iso = "https://1.1.1.1/image.iso"
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.assertRaises(exception.IloOperationError,
+ ilo_common.setup_uefi_https,
+ task, iso, True)
diff --git a/ironic/tests/unit/drivers/modules/test_image_utils.py b/ironic/tests/unit/drivers/modules/test_image_utils.py
index dc7750fb6..54204995b 100644
--- a/ironic/tests/unit/drivers/modules/test_image_utils.py
+++ b/ironic/tests/unit/drivers/modules/test_image_utils.py
@@ -314,18 +314,51 @@ class RedfishImageUtilsTestCase(db_base.DbTestCase):
root_uuid='1be26c0b-03f2-4d2e-ae87-c02d7f33c123',
base_iso='/path/to/baseiso')
+ def test__find_param(self):
+ param_dict = {
+ 'ilo_deploy_kernel': 'kernel',
+ 'ilo_deploy_ramdisk': 'ramdisk',
+ 'ilo_bootloader': 'bootloader'
+ }
+ param_str = "deploy_kernel"
+ expected = "kernel"
+
+ actual = image_utils._find_param(param_str, param_dict)
+ self.assertEqual(actual, expected)
+
+ def test__find_param_not_found(self):
+ param_dict = {
+ 'ilo_deploy_ramdisk': 'ramdisk',
+ 'ilo_bootloader': 'bootloader'
+ }
+ param_str = "deploy_kernel"
+ expected = None
+ actual = image_utils._find_param(param_str, param_dict)
+ self.assertEqual(actual, expected)
+
+ @mock.patch.object(image_utils, '_find_param', autospec=True)
@mock.patch.object(image_utils, '_prepare_iso_image', autospec=True)
- def test_prepare_deploy_iso(self, mock__prepare_iso_image):
+ def test_prepare_deploy_iso(self, mock__prepare_iso_image,
+ find_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
d_info = {
- 'deploy_kernel': 'kernel',
- 'deploy_ramdisk': 'ramdisk',
- 'bootloader': 'bootloader'
+ 'ilo_deploy_kernel': 'kernel',
+ 'ilo_deploy_ramdisk': 'ramdisk',
+ 'ilo_bootloader': 'bootloader'
}
task.node.driver_info.update(d_info)
+ find_call_list = [
+ mock.call('deploy_kernel', d_info),
+ mock.call('deploy_ramdisk', d_info),
+ mock.call('bootloader', d_info)
+ ]
+ find_mock.side_effect = [
+ 'kernel', 'ramdisk', 'bootloader'
+ ]
+
task.node.instance_info.update(deploy_boot_mode='uefi')
image_utils.prepare_deploy_iso(task, {}, 'deploy', d_info)
@@ -333,21 +366,33 @@ class RedfishImageUtilsTestCase(db_base.DbTestCase):
mock__prepare_iso_image.assert_called_once_with(
task, 'kernel', 'ramdisk', 'bootloader', params={})
+ find_mock.assert_has_calls(find_call_list)
+
+ @mock.patch.object(image_utils, '_find_param', autospec=True)
@mock.patch.object(image_utils, '_prepare_iso_image', autospec=True)
@mock.patch.object(images, 'create_vfat_image', autospec=True)
def test_prepare_deploy_iso_network_data(
- self, mock_create_vfat_image, mock__prepare_iso_image):
+ self, mock_create_vfat_image, mock__prepare_iso_image,
+ find_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
d_info = {
- 'deploy_kernel': 'kernel',
- 'deploy_ramdisk': 'ramdisk'
+ 'ilo_deploy_kernel': 'kernel',
+ 'ilo_deploy_ramdisk': 'ramdisk'
}
task.node.driver_info.update(d_info)
task.node.instance_info.update()
+ find_call_list = [
+ mock.call('deploy_kernel', d_info),
+ mock.call('deploy_ramdisk', d_info),
+ mock.call('bootloader', d_info)
+ ]
+ find_mock.side_effect = [
+ 'kernel', 'ramdisk', None
+ ]
network_data = {'a': ['b']}
mock_get_node_nw_data = mock.MagicMock(return_value=network_data)
@@ -362,16 +407,19 @@ class RedfishImageUtilsTestCase(db_base.DbTestCase):
task, 'kernel', 'ramdisk', bootloader_href=None,
configdrive=mock.ANY, params={})
+ find_mock.assert_has_calls(find_call_list)
+
+ @mock.patch.object(image_utils, '_find_param', autospec=True)
@mock.patch.object(image_utils, '_prepare_iso_image', autospec=True)
@mock.patch.object(images, 'create_boot_iso', autospec=True)
def test_prepare_boot_iso(self, mock_create_boot_iso,
- mock__prepare_iso_image):
+ mock__prepare_iso_image, find_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
d_info = {
- 'deploy_kernel': 'kernel',
- 'deploy_ramdisk': 'ramdisk',
- 'bootloader': 'bootloader'
+ 'ilo_deploy_kernel': 'kernel',
+ 'ilo_deploy_ramdisk': 'ramdisk',
+ 'ilo_bootloader': 'bootloader'
}
task.node.driver_info.update(d_info)
@@ -380,6 +428,14 @@ class RedfishImageUtilsTestCase(db_base.DbTestCase):
'kernel': 'http://kernel/img',
'ramdisk': 'http://ramdisk/img'})
+ find_call_list = [
+ mock.call('bootloader', d_info)
+ ]
+
+ find_mock.side_effect = [
+ 'bootloader'
+ ]
+
image_utils.prepare_boot_iso(
task, d_info, root_uuid=task.node.uuid)
@@ -388,10 +444,14 @@ class RedfishImageUtilsTestCase(db_base.DbTestCase):
'bootloader', root_uuid=task.node.uuid,
base_iso=None)
+ find_mock.assert_has_calls(find_call_list)
+
+ @mock.patch.object(image_utils, '_find_param', autospec=True)
@mock.patch.object(image_utils, '_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):
+ mock__prepare_iso_image,
+ find_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
d_info = {
@@ -404,6 +464,13 @@ class RedfishImageUtilsTestCase(db_base.DbTestCase):
task.node.instance_info.update(
{'boot_iso': 'http://boot/iso'})
+ find_call_list = [
+ mock.call('bootloader', d_info)
+ ]
+
+ find_mock.side_effect = [
+ 'bootloader'
+ ]
image_utils.prepare_boot_iso(
task, d_info, root_uuid=task.node.uuid)
@@ -411,3 +478,5 @@ class RedfishImageUtilsTestCase(db_base.DbTestCase):
mock.ANY, None, None,
'bootloader', root_uuid=task.node.uuid,
base_iso='http://boot/iso')
+
+ find_mock.assert_has_calls(find_call_list)
diff --git a/releasenotes/notes/add-ilo-uefi-https-boot-interface-f3b163a8a6243283.yaml b/releasenotes/notes/add-ilo-uefi-https-boot-interface-f3b163a8a6243283.yaml
new file mode 100644
index 000000000..06bc83094
--- /dev/null
+++ b/releasenotes/notes/add-ilo-uefi-https-boot-interface-f3b163a8a6243283.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ Adds ``ilo-uefi-https`` boot interface to ``ilo5`` hardware type.
+ This boot interface levereges the iLO UEFI firmware capability to
+ boot from given HTTPS URLs hosted securely over HTTPS webserver
+ with standard/custom certificates.
diff --git a/setup.cfg b/setup.cfg
index c314e21a5..f4beb96a5 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -69,6 +69,7 @@ ironic.hardware.interfaces.boot =
ilo-pxe = ironic.drivers.modules.ilo.boot:IloPXEBoot
ilo-ipxe = ironic.drivers.modules.ilo.boot:IloiPXEBoot
ilo-virtual-media = ironic.drivers.modules.ilo.boot:IloVirtualMediaBoot
+ ilo-uefi-https = ironic.drivers.modules.ilo.boot:IloUefiHttpsBoot
ipxe = ironic.drivers.modules.ipxe:iPXEBoot
irmc-pxe = ironic.drivers.modules.irmc.boot:IRMCPXEBoot
irmc-virtual-media = ironic.drivers.modules.irmc.boot:IRMCVirtualMediaBoot