summaryrefslogtreecommitdiff
path: root/ironic/drivers/modules
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 /ironic/drivers/modules
parentf1ea2ee6d1a7787cb86c77223283d6386379b68c (diff)
downloadironic-6d36b0b785b4b009fbb3e05d4dc5524faa71d6ba.tar.gz
Adds ilo-uefi-https boot interface to ilo5
Change-Id: I224eca4d8b331711369b17903098daa9fec27d7d Story: #2008073 Task: #40761
Diffstat (limited to 'ironic/drivers/modules')
-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
3 files changed, 455 insertions, 5 deletions
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,