summaryrefslogtreecommitdiff
path: root/ironic/drivers/modules/redfish/boot.py
diff options
context:
space:
mode:
Diffstat (limited to 'ironic/drivers/modules/redfish/boot.py')
-rw-r--r--ironic/drivers/modules/redfish/boot.py1033
1 files changed, 512 insertions, 521 deletions
diff --git a/ironic/drivers/modules/redfish/boot.py b/ironic/drivers/modules/redfish/boot.py
index f842a04fd..43a2e9b50 100644
--- a/ironic/drivers/modules/redfish/boot.py
+++ b/ironic/drivers/modules/redfish/boot.py
@@ -83,524 +83,610 @@ KERNEL_RAMDISK_LABELS = {
'rescue': RESCUE_PROPERTIES
}
+IMAGE_SUBDIR = 'redfish'
+
sushy = importutils.try_import('sushy')
-class RedfishVirtualMediaBoot(base.BootInterface):
- """Virtual media boot interface over Redfish.
+def _parse_driver_info(node):
+ """Gets the driver specific Node deployment info.
- Virtual Media allows booting the system from the "virtual"
- CD/DVD drive containing the user image that BMC "inserts"
- into the drive.
+ This method validates whether the 'driver_info' property of the
+ supplied node contains the required or optional information properly
+ for this driver to deploy images to the node.
- The CD/DVD images must be in ISO format and (depending on
- BMC implementation) could be pulled over HTTP, served as
- iSCSI targets or NFS volumes.
+ :param node: a target node of the deployment
+ :returns: the driver_info values of the node.
+ :raises: MissingParameterValue, if any of the required parameters are
+ missing.
+ :raises: InvalidParameterValue, if any of the parameters have invalid
+ value.
+ """
+ d_info = node.driver_info
- The baseline boot workflow looks like this:
+ mode = deploy_utils.rescue_or_deploy_mode(node)
+ params_to_check = KERNEL_RAMDISK_LABELS[mode]
- 1. Pull kernel, ramdisk and ESP (FAT partition image with EFI boot
- loader) images (ESP is only needed for UEFI boot)
- 2. Create bootable ISO out of images (#1), push it to Glance and
- pass to the BMC as Swift temporary URL
- 3. Optionally create floppy image with desired system configuration data,
- push it to Glance and pass to the BMC as Swift temporary URL
- 4. Insert CD/DVD and (optionally) floppy images and set proper boot mode
+ deploy_info = {option: d_info.get(option)
+ for option in params_to_check}
- For building deploy or rescue ISO, redfish boot interface uses
- `deploy_kernel`/`deploy_ramdisk` or `rescue_kernel`/`rescue_ramdisk`
- properties from `[instance_info]` or `[driver_info]`.
+ 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)
+ for k in params_to_check}
- For building boot (user) ISO, redfish boot interface seeks `kernel_id`
- and `ramdisk_id` properties in the Glance image metadata found in
- `[instance_info]image_source` node property.
- """
+ error_msg = _("Error validating Redfish virtual media. Some "
+ "parameters were missing in node's driver_info")
- IMAGE_SUBDIR = 'redfish'
+ deploy_utils.check_for_missing_params(deploy_info, error_msg)
- capabilities = ['iscsi_volume_boot', 'ramdisk_boot']
+ deploy_info.update(
+ {option: d_info.get(option, getattr(CONF.conductor, option, None))
+ for option in OPTIONAL_PROPERTIES})
- def __init__(self):
- """Initialize the Redfish virtual media boot interface.
+ deploy_info.update(redfish_utils.parse_driver_info(node))
- :raises: DriverLoadError if the driver can't be loaded due to
- missing dependencies
- """
- super(RedfishVirtualMediaBoot, self).__init__()
- if not sushy:
- raise exception.DriverLoadError(
- driver='redfish',
- reason=_('Unable to import the sushy library'))
+ return deploy_info
- @staticmethod
- def _parse_driver_info(node):
- """Gets the driver specific Node deployment info.
- This method validates whether the 'driver_info' property of the
- supplied node contains the required or optional information properly
- for this driver to deploy images to the node.
-
- :param node: a target node of the deployment
- :returns: the driver_info values of the node.
- :raises: MissingParameterValue, if any of the required parameters are
- missing.
- :raises: InvalidParameterValue, if any of the parameters have invalid
- value.
- """
- d_info = node.driver_info
+def _parse_instance_info(node):
+ """Gets the instance specific Node deployment info.
- mode = deploy_utils.rescue_or_deploy_mode(node)
- params_to_check = KERNEL_RAMDISK_LABELS[mode]
+ This method validates whether the 'instance_info' property of the
+ supplied node contains the required or optional information properly
+ for this driver to deploy images to the node.
- deploy_info = {option: d_info.get(option)
- for option in params_to_check}
+ :param node: a target node of the deployment
+ :returns: the instance_info values of the node.
+ :raises: InvalidParameterValue, if any of the parameters have invalid
+ value.
+ """
+ deploy_info = node.instance_info.copy()
- 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)
- for k in params_to_check}
+ # NOTE(etingof): this method is currently no-op, here for completeness
+ return deploy_info
- error_msg = _("Error validating Redfish virtual media. Some "
- "parameters were missing in node's driver_info")
- deploy_utils.check_for_missing_params(deploy_info, error_msg)
+def _append_filename_param(url, filename):
+ """Append 'filename=<file>' parameter to given URL.
- deploy_info.update(
- {option: d_info.get(option, getattr(CONF.conductor, option, None))
- for option in OPTIONAL_PROPERTIES})
+ Some BMCs seem to validate boot image URL requiring the URL to end
+ with something resembling ISO image file name.
- deploy_info.update(redfish_utils.parse_driver_info(node))
+ This function tries to add, hopefully, meaningless 'filename'
+ parameter to URL's query string in hope to make the entire boot image
+ URL looking more convincing to the BMC.
- return deploy_info
+ However, `url` with fragments might not get cured by this hack.
- @staticmethod
- def _parse_instance_info(node):
- """Gets the instance specific Node deployment info.
+ :param url: a URL to work on
+ :param filename: name of the file to append to the URL
+ :returns: original URL with 'filename' parameter appended
+ """
+ parsed_url = urlparse.urlparse(url)
+ parsed_qs = urlparse.parse_qsl(parsed_url.query)
- This method validates whether the 'instance_info' property of the
- supplied node contains the required or optional information properly
- for this driver to deploy images to the node.
+ has_filename = [x for x in parsed_qs if x[0].lower() == 'filename']
+ if has_filename:
+ return url
- :param node: a target node of the deployment
- :returns: the instance_info values of the node.
- :raises: InvalidParameterValue, if any of the parameters have invalid
- value.
- """
- deploy_info = node.instance_info.copy()
+ parsed_qs.append(('filename', filename))
+ parsed_url = list(parsed_url)
+ parsed_url[4] = urlparse.urlencode(parsed_qs)
- # NOTE(etingof): this method is currently no-op, here for completeness
- return deploy_info
+ return urlparse.urlunparse(parsed_url)
- @classmethod
- def _parse_deploy_info(cls, 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(cls._parse_driver_info(node))
- deploy_info.update(cls._parse_instance_info(node))
- return deploy_info
+def _get_floppy_image_name(node):
+ """Returns the floppy image name for a given node.
- @staticmethod
- def _append_filename_param(url, filename):
- """Append 'filename=<file>' parameter to given URL.
+ :param node: the node for which image name is to be provided.
+ """
+ return "image-%s" % node.uuid
- Some BMCs seem to validate boot image URL requiring the URL to end
- with something resembling ISO image file name.
- This function tries to add, hopefully, meaningless 'filename'
- parameter to URL's query string in hope to make the entire boot image
- URL looking more convincing to the BMC.
+def _get_iso_image_name(node):
+ """Returns the boot iso image name for a given node.
- However, `url` with fragments might not get cured by this hack.
+ :param node: the node for which image name is to be provided.
+ """
+ return "boot-%s" % node.uuid
- :param url: a URL to work on
- :param filename: name of the file to append to the URL
- :returns: original URL with 'filename' parameter appended
- """
- parsed_url = urlparse.urlparse(url)
- parsed_qs = urlparse.parse_qsl(parsed_url.query)
- has_filename = [x for x in parsed_qs if x[0].lower() == 'filename']
- if has_filename:
- return url
+def _insert_vmedia(task, boot_url, boot_device):
+ """Insert bootable ISO image into virtual CD or DVD
- parsed_qs.append(('filename', filename))
- parsed_url = list(parsed_url)
- parsed_url[4] = urlparse.urlencode(parsed_qs)
+ :param task: A task from TaskManager.
+ :param boot_url: URL to a bootable ISO image
+ :param boot_device: sushy boot device e.g. `VIRTUAL_MEDIA_CD`,
+ `VIRTUAL_MEDIA_DVD` or `VIRTUAL_MEDIA_FLOPPY`
+ :raises: InvalidParameterValue, if no suitable virtual CD or DVD is
+ found on the node.
+ """
+ system = redfish_utils.get_system(task.node)
+
+ for manager in system.managers:
+ for v_media in manager.virtual_media.get_members():
+ if boot_device not in v_media.media_types:
+ continue
+
+ if v_media.inserted:
+ if v_media.image == boot_url:
+ LOG.debug("Boot media %(boot_url)s is already "
+ "inserted into %(boot_device)s for node "
+ "%(node)s", {'node': task.node.uuid,
+ 'boot_url': boot_url,
+ 'boot_device': boot_device})
+ return
+
+ continue
+
+ v_media.insert_media(boot_url, inserted=True,
+ write_protected=True)
+
+ LOG.info("Inserted boot media %(boot_url)s into "
+ "%(boot_device)s for node "
+ "%(node)s", {'node': task.node.uuid,
+ 'boot_url': boot_url,
+ 'boot_device': boot_device})
+ return
- return urlparse.urlunparse(parsed_url)
+ raise exception.InvalidParameterValue(
+ _('No suitable virtual media device found'))
- @classmethod
- def _publish_image(cls, image_file, object_name):
- """Make image file downloadable.
- Depending on ironic settings, pushes given file into Swift or copies
- it over to local HTTP server's document root and returns publicly
- accessible URL leading to the given file.
+def _eject_vmedia(task, boot_device=None):
+ """Eject virtual CDs and DVDs
- :param image_file: path to file to publish
- :param object_name: name of the published file
- :return: a URL to download published file
- """
+ :param task: A task from TaskManager.
+ :param boot_device: sushy boot device e.g. `VIRTUAL_MEDIA_CD`,
+ `VIRTUAL_MEDIA_DVD` or `VIRTUAL_MEDIA_FLOPPY` or `None` to
+ eject everything (default).
+ :raises: InvalidParameterValue, if no suitable virtual CD or DVD is
+ found on the node.
+ """
+ system = redfish_utils.get_system(task.node)
- if CONF.redfish.use_swift:
- container = CONF.redfish.swift_container
- timeout = CONF.redfish.swift_object_expiry_timeout
+ for manager in system.managers:
+ for v_media in manager.virtual_media.get_members():
+ if boot_device and boot_device not in v_media.media_types:
+ continue
- object_headers = {'X-Delete-After': str(timeout)}
+ inserted = v_media.inserted
- swift_api = swift.SwiftAPI()
+ if inserted:
+ v_media.eject_media()
- swift_api.create_object(container, object_name, image_file,
- object_headers=object_headers)
+ LOG.info("Boot media is%(already)s ejected from "
+ "%(boot_device)s for node %(node)s"
+ "", {'node': task.node.uuid,
+ 'already': '' if inserted else ' already',
+ 'boot_device': v_media.name})
- image_url = swift_api.get_temp_url(container, object_name, timeout)
- else:
- public_dir = os.path.join(CONF.deploy.http_root, cls.IMAGE_SUBDIR)
+def _has_vmedia_device(task, boot_device):
+ """Indicate if device exists at any of the managers
- if not os.path.exists(public_dir):
- os.mkdir(public_dir, 0x755)
+ :param task: A task from TaskManager.
+ :param boot_device: sushy boot device e.g. `VIRTUAL_MEDIA_CD`,
+ `VIRTUAL_MEDIA_DVD` or `VIRTUAL_MEDIA_FLOPPY`.
+ """
+ system = redfish_utils.get_system(task.node)
- published_file = os.path.join(public_dir, object_name)
+ for manager in system.managers:
+ for v_media in manager.virtual_media.get_members():
+ if boot_device in v_media.media_types:
+ return True
- try:
- os.link(image_file, published_file)
- except OSError as exc:
- LOG.debug(
- "Could not hardlink image file %(image)s to public "
- "location %(public)s (will copy it over): "
- "%(error)s", {'image': image_file,
- 'public': published_file,
- 'error': exc})
+def _cleanup_iso_image(task):
+ """Deletes the ISO if it was created for the instance.
- shutil.copyfile(image_file, published_file)
+ :param task: A task from TaskManager.
+ """
+ iso_object_name = _get_iso_image_name(task.node)
- image_url = os.path.join(
- CONF.deploy.http_url, cls.IMAGE_SUBDIR, object_name)
+ _unpublish_image(iso_object_name)
- image_url = cls._append_filename_param(
- image_url, os.path.basename(image_file))
- return image_url
+def _unpublish_image(object_name):
+ """Withdraw the image previously made downloadable.
- @classmethod
- def _unpublish_image(cls, object_name):
- """Withdraw the image previously made downloadable.
+ Depending on ironic settings, removes previously published file
+ from where it has been published - Swift or local HTTP server's
+ document root.
- Depending on ironic settings, removes previously published file
- from where it has been published - Swift or local HTTP server's
- document root.
+ :param object_name: name of the published file (optional)
+ """
+ if CONF.redfish.use_swift:
+ container = CONF.redfish.swift_container
- :param object_name: name of the published file (optional)
- """
- if CONF.redfish.use_swift:
- container = CONF.redfish.swift_container
+ swift_api = swift.SwiftAPI()
- swift_api = swift.SwiftAPI()
+ LOG.debug("Cleaning up image %(name)s from Swift container "
+ "%(container)s", {'name': object_name,
+ 'container': container})
- LOG.debug("Cleaning up image %(name)s from Swift container "
- "%(container)s", {'name': object_name,
- 'container': container})
+ try:
+ swift_api.delete_object(container, object_name)
+
+ except exception.SwiftOperationError as exc:
+ LOG.warning("Failed to clean up image %(image)s. Error: "
+ "%(error)s.", {'image': object_name,
+ 'error': exc})
+
+ else:
+ published_file = os.path.join(
+ CONF.deploy.http_root, IMAGE_SUBDIR, object_name)
+
+ ironic_utils.unlink_without_raise(published_file)
+
+
+def _prepare_floppy_image(task, params=None):
+ """Prepares the floppy image for passing the parameters.
+
+ This method prepares a temporary VFAT filesystem image and adds
+ a file into the image which contains parameters to be passed to
+ the ramdisk. Then this method uploads built image to Swift
+ '[redfish]swift_container', setting it to auto expire after
+ '[redfish]swift_object_expiry_timeout' seconds. Finally, a
+ temporary Swift URL is returned addressing Swift object just
+ created.
+
+ :param task: a TaskManager instance containing the node to act on.
+ :param params: a dictionary containing 'parameter name'->'value'
+ mapping to be passed to deploy or rescue image via floppy image.
+ :raises: ImageCreationFailed, if it failed while creating the floppy
+ image.
+ :raises: SwiftOperationError, if any operation with Swift fails.
+ :returns: image URL for the floppy image.
+ """
+ object_name = _get_floppy_image_name(task.node)
- try:
- swift_api.delete_object(container, object_name)
+ LOG.debug("Trying to create floppy image for node "
+ "%(node)s", {'node': task.node.uuid})
- except exception.SwiftOperationError as exc:
- LOG.warning("Failed to clean up image %(image)s. Error: "
- "%(error)s.", {'image': object_name,
- 'error': exc})
+ with tempfile.NamedTemporaryFile(
+ dir=CONF.tempdir, suffix='.img') as vfat_image_tmpfile_obj:
- else:
- published_file = os.path.join(
- CONF.deploy.http_root, cls.IMAGE_SUBDIR, object_name)
+ vfat_image_tmpfile = vfat_image_tmpfile_obj.name
+ images.create_vfat_image(vfat_image_tmpfile, parameters=params)
- ironic_utils.unlink_without_raise(published_file)
+ image_url = _publish_image(vfat_image_tmpfile, object_name)
- @staticmethod
- def _get_floppy_image_name(node):
- """Returns the floppy image name for a given node.
+ LOG.debug("Created floppy image %(name)s in Swift for node %(node)s, "
+ "exposed as temporary URL "
+ "%(url)s", {'node': task.node.uuid,
+ 'name': object_name,
+ 'url': image_url})
- :param node: the node for which image name is to be provided.
- """
- return "image-%s" % node.uuid
+ return image_url
- @classmethod
- def _cleanup_floppy_image(cls, task):
- """Deletes the floppy image if it was created for the node.
- :param task: an ironic node object.
- """
- floppy_object_name = cls._get_floppy_image_name(task.node)
+def _publish_image(image_file, object_name):
+ """Make image file downloadable.
- cls._unpublish_image(floppy_object_name)
+ Depending on ironic settings, pushes given file into Swift or copies
+ it over to local HTTP server's document root and returns publicly
+ accessible URL leading to the given file.
- @classmethod
- def _prepare_floppy_image(cls, task, params=None):
- """Prepares the floppy image for passing the parameters.
+ :param image_file: path to file to publish
+ :param object_name: name of the published file
+ :return: a URL to download published file
+ """
- This method prepares a temporary VFAT filesystem image and adds
- a file into the image which contains parameters to be passed to
- the ramdisk. Then this method uploads built image to Swift
- '[redfish]swift_container', setting it to auto expire after
- '[redfish]swift_object_expiry_timeout' seconds. Finally, a
- temporary Swift URL is returned addressing Swift object just
- created.
+ if CONF.redfish.use_swift:
+ container = CONF.redfish.swift_container
+ timeout = CONF.redfish.swift_object_expiry_timeout
- :param task: a TaskManager instance containing the node to act on.
- :param params: a dictionary containing 'parameter name'->'value'
- mapping to be passed to deploy or rescue image via floppy image.
- :raises: ImageCreationFailed, if it failed while creating the floppy
- image.
- :raises: SwiftOperationError, if any operation with Swift fails.
- :returns: image URL for the floppy image.
- """
- object_name = cls._get_floppy_image_name(task.node)
+ object_headers = {'X-Delete-After': str(timeout)}
- LOG.debug("Trying to create floppy image for node "
- "%(node)s", {'node': task.node.uuid})
+ swift_api = swift.SwiftAPI()
- with tempfile.NamedTemporaryFile(
- dir=CONF.tempdir, suffix='.img') as vfat_image_tmpfile_obj:
+ swift_api.create_object(container, object_name, image_file,
+ object_headers=object_headers)
- vfat_image_tmpfile = vfat_image_tmpfile_obj.name
- images.create_vfat_image(vfat_image_tmpfile, parameters=params)
+ image_url = swift_api.get_temp_url(container, object_name, timeout)
- image_url = cls._publish_image(vfat_image_tmpfile, object_name)
+ else:
+ public_dir = os.path.join(CONF.deploy.http_root, IMAGE_SUBDIR)
- LOG.debug("Created floppy image %(name)s in Swift for node %(node)s, "
- "exposed as temporary URL "
- "%(url)s", {'node': task.node.uuid,
- 'name': object_name,
- 'url': image_url})
+ if not os.path.exists(public_dir):
+ os.mkdir(public_dir, 0x755)
- return image_url
+ published_file = os.path.join(public_dir, object_name)
- @staticmethod
- def _get_iso_image_name(node):
- """Returns the boot iso image name for a given node.
+ try:
+ os.link(image_file, published_file)
- :param node: the node for which image name is to be provided.
- """
- return "boot-%s" % node.uuid
+ except OSError as exc:
+ LOG.debug(
+ "Could not hardlink image file %(image)s to public "
+ "location %(public)s (will copy it over): "
+ "%(error)s", {'image': image_file,
+ 'public': published_file,
+ 'error': exc})
- @classmethod
- def _cleanup_iso_image(cls, task):
- """Deletes the ISO if it was created for the instance.
+ shutil.copyfile(image_file, published_file)
- :param task: an ironic node object.
- """
- iso_object_name = cls._get_iso_image_name(task.node)
+ image_url = os.path.join(
+ CONF.deploy.http_url, IMAGE_SUBDIR, object_name)
- cls._unpublish_image(iso_object_name)
+ image_url = _append_filename_param(
+ image_url, os.path.basename(image_file))
- @classmethod
- def _prepare_iso_image(cls, task, kernel_href, ramdisk_href,
- bootloader_href=None, configdrive=None,
- root_uuid=None, params=None):
- """Prepare an ISO to boot the node.
+ return image_url
- Build bootable ISO out of `kernel_href` and `ramdisk_href` (and
- `bootloader` if it's UEFI boot), then push built image up to Swift and
- return a temporary URL.
- :param task: a TaskManager instance containing the node to act on.
- :param kernel_href: URL or Glance UUID of the kernel to use
- :param ramdisk_href: URL or Glance UUID of the ramdisk to use
- :param bootloader_href: URL or Glance UUID of the EFI bootloader
- image to use when creating UEFI bootbable ISO
- :param configdrive: URL to or a compressed blob of a ISO9660 or
- FAT-formatted OpenStack config drive image. This image will be
- written onto the built ISO image. Optional.
- :param root_uuid: optional uuid of the root partition.
- :param params: a dictionary containing 'parameter name'->'value'
- mapping to be passed to kernel command line.
- :returns: bootable ISO HTTP URL.
- :raises: MissingParameterValue, if any of the required parameters are
- missing.
- :raises: InvalidParameterValue, if any of the parameters have invalid
- value.
- :raises: ImageCreationFailed, if creating ISO image failed.
- """
- if not kernel_href or not ramdisk_href:
- raise exception.InvalidParameterValue(_(
- "Unable to find kernel or ramdisk for "
- "building ISO for %(node)s") %
- {'node': task.node.uuid})
+def _cleanup_floppy_image(task):
+ """Deletes the floppy image if it was created for the node.
- i_info = task.node.instance_info
+ :param task: an ironic node object.
+ """
+ floppy_object_name = _get_floppy_image_name(task.node)
- if deploy_utils.get_boot_option(task.node) == "ramdisk":
- kernel_params = "root=/dev/ram0 text "
- kernel_params += i_info.get("ramdisk_kernel_arguments", "")
+ _unpublish_image(floppy_object_name)
- else:
- kernel_params = i_info.get(
- 'kernel_append_params', CONF.redfish.kernel_append_params)
-
- if params:
- kernel_params = ' '.join(
- (kernel_params, ' '.join(
- '%s=%s' % kv for kv in params.items())))
-
- boot_mode = boot_mode_utils.get_boot_mode_for_deploy(task.node)
-
- LOG.debug("Trying to create %(boot_mode)s ISO image for node %(node)s "
- "with kernel %(kernel_href)s, ramdisk %(ramdisk_href)s, "
- "bootloader %(bootloader_href)s and kernel params %(params)s"
- "", {'node': task.node.uuid,
- 'boot_mode': boot_mode,
- 'kernel_href': kernel_href,
- 'ramdisk_href': ramdisk_href,
- 'bootloader_href': bootloader_href,
- 'params': kernel_params})
- with tempfile.NamedTemporaryFile(
- dir=CONF.tempdir, suffix='.iso') as boot_fileobj:
+def _parse_deploy_info(node):
+ """Gets the instance and driver specific Node deployment info.
- with tempfile.NamedTemporaryFile(
- dir=CONF.tempdir, suffix='.img') as cfgdrv_fileobj:
+ 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.
- configdrive_href = configdrive
+ :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(_parse_driver_info(node))
+ deploy_info.update(_parse_instance_info(node))
+
+ return deploy_info
+
+
+def _prepare_iso_image(task, kernel_href, ramdisk_href,
+ bootloader_href=None, configdrive=None,
+ root_uuid=None, params=None):
+ """Prepare an ISO to boot the node.
+
+ Build bootable ISO out of `kernel_href` and `ramdisk_href` (and
+ `bootloader` if it's UEFI boot), then push built image up to Swift and
+ return a temporary URL.
+
+ :param task: a TaskManager instance containing the node to act on.
+ :param kernel_href: URL or Glance UUID of the kernel to use
+ :param ramdisk_href: URL or Glance UUID of the ramdisk to use
+ :param bootloader_href: URL or Glance UUID of the EFI bootloader
+ image to use when creating UEFI bootbable ISO
+ :param configdrive: URL to or a compressed blob of a ISO9660 or
+ FAT-formatted OpenStack config drive image. This image will be
+ written onto the built ISO image. Optional.
+ :param root_uuid: optional uuid of the root partition.
+ :param params: a dictionary containing 'parameter name'->'value'
+ mapping to be passed to kernel command line.
+ :returns: bootable ISO HTTP URL.
+ :raises: MissingParameterValue, if any of the required parameters are
+ missing.
+ :raises: InvalidParameterValue, if any of the parameters have invalid
+ value.
+ :raises: ImageCreationFailed, if creating ISO image failed.
+ """
+ if not kernel_href or not ramdisk_href:
+ raise exception.InvalidParameterValue(_(
+ "Unable to find kernel or ramdisk for "
+ "building ISO for %(node)s") %
+ {'node': task.node.uuid})
+
+ i_info = task.node.instance_info
+
+ if deploy_utils.get_boot_option(task.node) == "ramdisk":
+ kernel_params = "root=/dev/ram0 text "
+ kernel_params += i_info.get("ramdisk_kernel_arguments", "")
+
+ else:
+ kernel_params = i_info.get(
+ 'kernel_append_params', CONF.redfish.kernel_append_params)
+
+ if params:
+ kernel_params = ' '.join(
+ (kernel_params, ' '.join(
+ '%s=%s' % kv for kv in params.items())))
+
+ boot_mode = boot_mode_utils.get_boot_mode_for_deploy(task.node)
+
+ LOG.debug("Trying to create %(boot_mode)s ISO image for node %(node)s "
+ "with kernel %(kernel_href)s, ramdisk %(ramdisk_href)s, "
+ "bootloader %(bootloader_href)s and kernel params %(params)s"
+ "", {'node': task.node.uuid,
+ 'boot_mode': boot_mode,
+ 'kernel_href': kernel_href,
+ 'ramdisk_href': ramdisk_href,
+ 'bootloader_href': bootloader_href,
+ 'params': kernel_params})
+
+ with tempfile.NamedTemporaryFile(
+ dir=CONF.tempdir, suffix='.iso') as boot_fileobj:
- if configdrive:
- parsed_url = urlparse.urlparse(configdrive)
- if not parsed_url.scheme:
- cfgdrv_blob = base64.decode_as_bytes(configdrive)
+ with tempfile.NamedTemporaryFile(
+ dir=CONF.tempdir, suffix='.img') as cfgdrv_fileobj:
+
+ configdrive_href = configdrive
+
+ if configdrive:
+ parsed_url = urlparse.urlparse(configdrive)
+ if not parsed_url.scheme:
+ cfgdrv_blob = base64.decode_as_bytes(configdrive)
+
+ with open(cfgdrv_fileobj.name, 'wb') as f:
+ f.write(cfgdrv_blob)
+
+ configdrive_href = urlparse.urlunparse(
+ ('file', '', cfgdrv_fileobj.name, '', '', ''))
+
+ LOG.info("Burning configdrive %(url)s to boot ISO image "
+ "for node %(node)s", {'url': configdrive_href,
+ 'node': task.node.uuid})
+
+ boot_iso_tmp_file = boot_fileobj.name
+ images.create_boot_iso(
+ task.context, boot_iso_tmp_file,
+ kernel_href, ramdisk_href,
+ esp_image_href=bootloader_href,
+ configdrive_href=configdrive_href,
+ root_uuid=root_uuid,
+ kernel_params=kernel_params,
+ boot_mode=boot_mode)
+
+ iso_object_name = _get_iso_image_name(task.node)
+
+ image_url = _publish_image(
+ boot_iso_tmp_file, iso_object_name)
+
+ LOG.debug("Created ISO %(name)s in object store for node %(node)s, "
+ "exposed as temporary URL "
+ "%(url)s", {'node': task.node.uuid,
+ 'name': iso_object_name,
+ 'url': image_url})
+
+ return image_url
+
+
+def _prepare_deploy_iso(task, params, mode):
+ """Prepare deploy or rescue ISO image
+
+ Build bootable ISO out of
+ `[driver_info]/deploy_kernel`/`[driver_info]/deploy_ramdisk` or
+ `[driver_info]/rescue_kernel`/`[driver_info]/rescue_ramdisk`
+ and `[driver_info]/bootloader`, then push built image up to Glance
+ and return temporary Swift URL to the image.
+
+ :param task: a TaskManager instance containing the node to act on.
+ :param params: a dictionary containing 'parameter name'->'value'
+ mapping to be passed to kernel command line.
+ :param mode: either 'deploy' or 'rescue'.
+ :returns: bootable ISO HTTP URL.
+ :raises: MissingParameterValue, if any of the required parameters are
+ missing.
+ :raises: InvalidParameterValue, if any of the parameters have invalid
+ value.
+ :raises: ImageCreationFailed, if creating ISO image failed.
+ """
+ node = task.node
- with open(cfgdrv_fileobj.name, 'wb') as f:
- f.write(cfgdrv_blob)
+ d_info = _parse_driver_info(node)
- configdrive_href = urlparse.urlunparse(
- ('file', '', cfgdrv_fileobj.name, '', '', ''))
+ kernel_href = d_info.get('%s_kernel' % mode)
+ ramdisk_href = d_info.get('%s_ramdisk' % mode)
+ bootloader_href = d_info.get('bootloader')
- LOG.info("Burning configdrive %(url)s to boot ISO image "
- "for node %(node)s", {'url': configdrive_href,
- 'node': task.node.uuid})
+ return _prepare_iso_image(
+ task, kernel_href, ramdisk_href, bootloader_href, params=params)
- boot_iso_tmp_file = boot_fileobj.name
- images.create_boot_iso(
- task.context, boot_iso_tmp_file,
- kernel_href, ramdisk_href,
- esp_image_href=bootloader_href,
- configdrive_href=configdrive_href,
- root_uuid=root_uuid,
- kernel_params=kernel_params,
- boot_mode=boot_mode)
- iso_object_name = cls._get_iso_image_name(task.node)
+def _prepare_boot_iso(task, root_uuid=None):
+ """Prepare boot ISO image
- image_url = cls._publish_image(
- boot_iso_tmp_file, iso_object_name)
+ Build bootable ISO out of `[instance_info]/kernel`,
+ `[instance_info]/ramdisk` and `[driver_info]/bootloader` if present.
+ Otherwise, read `kernel_id` and `ramdisk_id` from
+ `[instance_info]/image_source` Glance image metadata.
- LOG.debug("Created ISO %(name)s in object store for node %(node)s, "
- "exposed as temporary URL "
- "%(url)s", {'node': task.node.uuid,
- 'name': iso_object_name,
- 'url': image_url})
+ Push produced ISO image up to Glance and return temporary Swift
+ URL to the image.
- return image_url
+ :param task: a TaskManager instance containing the node to act on.
+ :returns: bootable ISO HTTP URL.
+ :raises: MissingParameterValue, if any of the required parameters are
+ missing.
+ :raises: InvalidParameterValue, if any of the parameters have invalid
+ value.
+ :raises: ImageCreationFailed, if creating ISO image failed.
+ """
+ node = task.node
- @classmethod
- def _prepare_deploy_iso(cls, task, params, mode):
- """Prepare deploy or rescue ISO image
+ d_info = _parse_deploy_info(node)
- Build bootable ISO out of
- `[driver_info]/deploy_kernel`/`[driver_info]/deploy_ramdisk` or
- `[driver_info]/rescue_kernel`/`[driver_info]/rescue_ramdisk`
- and `[driver_info]/bootloader`, then push built image up to Glance
- and return temporary Swift URL to the image.
+ kernel_href = node.instance_info.get('kernel')
+ ramdisk_href = node.instance_info.get('ramdisk')
- :param task: a TaskManager instance containing the node to act on.
- :param params: a dictionary containing 'parameter name'->'value'
- mapping to be passed to kernel command line.
- :param mode: either 'deploy' or 'rescue'.
- :returns: bootable ISO HTTP URL.
- :raises: MissingParameterValue, if any of the required parameters are
- missing.
- :raises: InvalidParameterValue, if any of the parameters have invalid
- value.
- :raises: ImageCreationFailed, if creating ISO image failed.
- """
- node = task.node
+ if not kernel_href or not ramdisk_href:
- d_info = cls._parse_driver_info(node)
+ image_href = d_info['image_source']
- kernel_href = d_info.get('%s_kernel' % mode)
- ramdisk_href = d_info.get('%s_ramdisk' % mode)
- bootloader_href = d_info.get('bootloader')
+ image_properties = (
+ images.get_image_properties(
+ task.context, image_href, ['kernel_id', 'ramdisk_id']))
- return cls._prepare_iso_image(
- task, kernel_href, ramdisk_href, bootloader_href, params=params)
+ if not kernel_href:
+ kernel_href = image_properties.get('kernel_id')
- @classmethod
- def _prepare_boot_iso(cls, task, root_uuid=None):
- """Prepare boot ISO image
+ if not ramdisk_href:
+ ramdisk_href = image_properties.get('ramdisk_id')
- Build bootable ISO out of `[instance_info]/kernel`,
- `[instance_info]/ramdisk` and `[driver_info]/bootloader` if present.
- Otherwise, read `kernel_id` and `ramdisk_id` from
- `[instance_info]/image_source` Glance image metadata.
+ if not kernel_href or not ramdisk_href:
+ raise exception.InvalidParameterValue(_(
+ "Unable to find kernel or ramdisk for "
+ "to generate boot ISO for %(node)s") %
+ {'node': task.node.uuid})
- Push produced ISO image up to Glance and return temporary Swift
- URL to the image.
+ bootloader_href = d_info.get('bootloader')
- :param task: a TaskManager instance containing the node to act on.
- :returns: bootable ISO HTTP URL.
- :raises: MissingParameterValue, if any of the required parameters are
- missing.
- :raises: InvalidParameterValue, if any of the parameters have invalid
- value.
- :raises: ImageCreationFailed, if creating ISO image failed.
- """
- node = task.node
+ return _prepare_iso_image(
+ task, kernel_href, ramdisk_href, bootloader_href,
+ root_uuid=root_uuid)
- d_info = cls._parse_deploy_info(node)
- kernel_href = node.instance_info.get('kernel')
- ramdisk_href = node.instance_info.get('ramdisk')
+class RedfishVirtualMediaBoot(base.BootInterface):
+ """Virtual media boot interface over Redfish.
- if not kernel_href or not ramdisk_href:
+ Virtual Media allows booting the system from the "virtual"
+ CD/DVD drive containing the user image that BMC "inserts"
+ into the drive.
- image_href = d_info['image_source']
+ The CD/DVD images must be in ISO format and (depending on
+ BMC implementation) could be pulled over HTTP, served as
+ iSCSI targets or NFS volumes.
- image_properties = (
- images.get_image_properties(
- task.context, image_href, ['kernel_id', 'ramdisk_id']))
+ The baseline boot workflow looks like this:
- if not kernel_href:
- kernel_href = image_properties.get('kernel_id')
+ 1. Pull kernel, ramdisk and ESP (FAT partition image with EFI boot
+ loader) images (ESP is only needed for UEFI boot)
+ 2. Create bootable ISO out of images (#1), push it to Glance and
+ pass to the BMC as Swift temporary URL
+ 3. Optionally create floppy image with desired system configuration data,
+ push it to Glance and pass to the BMC as Swift temporary URL
+ 4. Insert CD/DVD and (optionally) floppy images and set proper boot mode
- if not ramdisk_href:
- ramdisk_href = image_properties.get('ramdisk_id')
+ For building deploy or rescue ISO, redfish boot interface uses
+ `deploy_kernel`/`deploy_ramdisk` or `rescue_kernel`/`rescue_ramdisk`
+ properties from `[instance_info]` or `[driver_info]`.
+
+ For building boot (user) ISO, redfish boot interface seeks `kernel_id`
+ and `ramdisk_id` properties in the Glance image metadata found in
+ `[instance_info]image_source` node property.
+ """
- if not kernel_href or not ramdisk_href:
- raise exception.InvalidParameterValue(_(
- "Unable to find kernel or ramdisk for "
- "to generate boot ISO for %(node)s") %
- {'node': task.node.uuid})
+ capabilities = ['iscsi_volume_boot', 'ramdisk_boot']
- bootloader_href = d_info.get('bootloader')
+ def __init__(self):
+ """Initialize the Redfish virtual media boot interface.
- return cls._prepare_iso_image(
- task, kernel_href, ramdisk_href, bootloader_href,
- root_uuid=root_uuid)
+ :raises: DriverLoadError if the driver can't be loaded due to
+ missing dependencies
+ """
+ super(RedfishVirtualMediaBoot, self).__init__()
+ if not sushy:
+ raise exception.DriverLoadError(
+ driver='redfish',
+ reason=_('Unable to import the sushy library'))
def get_properties(self):
"""Return the properties of the interface.
@@ -609,8 +695,7 @@ class RedfishVirtualMediaBoot(base.BootInterface):
"""
return REQUIRED_PROPERTIES
- @classmethod
- def _validate_driver_info(cls, task):
+ def _validate_driver_info(self, task):
"""Validate the prerequisites for virtual media based boot.
This method validates whether the 'driver_info' property of the
@@ -623,10 +708,9 @@ class RedfishVirtualMediaBoot(base.BootInterface):
"""
node = task.node
- cls._parse_driver_info(node)
+ _parse_driver_info(node)
- @classmethod
- def _validate_instance_info(cls, task):
+ def _validate_instance_info(self, task):
"""Validate instance image information for the task's node.
This method validates whether the 'instance_info' property of the
@@ -639,7 +723,7 @@ class RedfishVirtualMediaBoot(base.BootInterface):
"""
node = task.node
- d_info = cls._parse_deploy_info(node)
+ d_info = _parse_deploy_info(node)
if node.driver_internal_info.get('is_whole_disk_image'):
props = []
@@ -720,7 +804,7 @@ class RedfishVirtualMediaBoot(base.BootInterface):
manager_utils.node_power_action(task, states.POWER_OFF)
- d_info = self._parse_driver_info(node)
+ d_info = _parse_driver_info(node)
config_via_floppy = d_info.get('config_via_floppy')
@@ -731,16 +815,16 @@ class RedfishVirtualMediaBoot(base.BootInterface):
if config_via_floppy:
- if self._has_vmedia_device(task, sushy.VIRTUAL_MEDIA_FLOPPY):
+ if _has_vmedia_device(task, sushy.VIRTUAL_MEDIA_FLOPPY):
# NOTE (etingof): IPA will read the diskette only if
# we tell it to
ramdisk_params['boot_method'] = 'vmedia'
- floppy_ref = self._prepare_floppy_image(
+ floppy_ref = _prepare_floppy_image(
task, params=ramdisk_params)
- self._eject_vmedia(task, sushy.VIRTUAL_MEDIA_FLOPPY)
- self._insert_vmedia(
+ _eject_vmedia(task, sushy.VIRTUAL_MEDIA_FLOPPY)
+ _insert_vmedia(
task, floppy_ref, sushy.VIRTUAL_MEDIA_FLOPPY)
LOG.debug('Inserted virtual floppy with configuration for '
@@ -753,10 +837,10 @@ class RedfishVirtualMediaBoot(base.BootInterface):
mode = deploy_utils.rescue_or_deploy_mode(node)
- iso_ref = self._prepare_deploy_iso(task, ramdisk_params, mode)
+ iso_ref = _prepare_deploy_iso(task, ramdisk_params, mode)
- self._eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
- self._insert_vmedia(task, iso_ref, sushy.VIRTUAL_MEDIA_CD)
+ _eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
+ _insert_vmedia(task, iso_ref, sushy.VIRTUAL_MEDIA_CD)
boot_mode_utils.sync_boot_mode(task)
@@ -775,22 +859,21 @@ class RedfishVirtualMediaBoot(base.BootInterface):
:param task: A task from TaskManager.
:returns: None
"""
- node = task.node
-
- d_info = self._parse_driver_info(node)
+ d_info = _parse_driver_info(task.node)
config_via_floppy = d_info.get('config_via_floppy')
LOG.debug("Cleaning up deploy boot for "
"%(node)s", {'node': task.node.uuid})
- self._eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
- self._cleanup_iso_image(task)
+ _eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
+ _cleanup_iso_image(task)
if (config_via_floppy
- and self._has_vmedia_device(task, sushy.VIRTUAL_MEDIA_FLOPPY)):
- self._eject_vmedia(task, sushy.VIRTUAL_MEDIA_FLOPPY)
- self._cleanup_floppy_image(task)
+ and _has_vmedia_device(task, sushy.VIRTUAL_MEDIA_FLOPPY)):
+ _eject_vmedia(task, sushy.VIRTUAL_MEDIA_FLOPPY)
+
+ _cleanup_floppy_image(task)
def prepare_instance(self, task):
"""Prepares the boot of instance over virtual media.
@@ -815,12 +898,10 @@ class RedfishVirtualMediaBoot(base.BootInterface):
node = task.node
boot_option = deploy_utils.get_boot_option(node)
-
self.clean_up_instance(task)
iwdi = node.driver_internal_info.get('is_whole_disk_image')
if boot_option == "local" or iwdi:
- self._set_boot_device(
- task, boot_devices.DISK, persistent=True)
+ self._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,
@@ -831,28 +912,24 @@ class RedfishVirtualMediaBoot(base.BootInterface):
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)
- self._set_boot_device(
- task, boot_devices.DISK, persistent=True)
+ self._set_boot_device(task, boot_devices.DISK, persistent=True)
return
params.update(root_uuid=root_uuid)
- iso_ref = self._prepare_boot_iso(task, **params)
-
- self._eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
- self._insert_vmedia(task, iso_ref, sushy.VIRTUAL_MEDIA_CD)
+ iso_ref = _prepare_boot_iso(task, **params)
+ _eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
+ _insert_vmedia(task, iso_ref, sushy.VIRTUAL_MEDIA_CD)
boot_mode_utils.sync_boot_mode(task)
- self._set_boot_device(
- task, boot_devices.CDROM, persistent=True)
+ self._set_boot_device(task, boot_devices.CDROM, persistent=True)
LOG.debug("Node %(node)s is set to permanently boot from "
"%(device)s", {'node': task.node.uuid,
@@ -870,99 +947,13 @@ class RedfishVirtualMediaBoot(base.BootInterface):
LOG.debug("Cleaning up instance boot for "
"%(node)s", {'node': task.node.uuid})
- self._eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
+ _eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
d_info = task.node.driver_info
config_via_floppy = d_info.get('config_via_floppy')
if config_via_floppy:
- self._eject_vmedia(task, sushy.VIRTUAL_MEDIA_FLOPPY)
-
- self._cleanup_iso_image(task)
-
- @staticmethod
- def _insert_vmedia(task, boot_url, boot_device):
- """Insert bootable ISO image into virtual CD or DVD
-
- :param task: A task from TaskManager.
- :param boot_url: URL to a bootable ISO image
- :param boot_device: sushy boot device e.g. `VIRTUAL_MEDIA_CD`,
- `VIRTUAL_MEDIA_DVD` or `VIRTUAL_MEDIA_FLOPPY`
- :raises: InvalidParameterValue, if no suitable virtual CD or DVD is
- found on the node.
- """
- system = redfish_utils.get_system(task.node)
-
- for manager in system.managers:
- for v_media in manager.virtual_media.get_members():
- if boot_device not in v_media.media_types:
- continue
-
- if v_media.inserted:
- if v_media.image == boot_url:
- LOG.debug("Boot media %(boot_url)s is already "
- "inserted into %(boot_device)s for node "
- "%(node)s", {'node': task.node.uuid,
- 'boot_url': boot_url,
- 'boot_device': boot_device})
- return
-
- continue
-
- v_media.insert_media(boot_url, inserted=True,
- write_protected=True)
-
- LOG.info("Inserted boot media %(boot_url)s into "
- "%(boot_device)s for node "
- "%(node)s", {'node': task.node.uuid,
- 'boot_url': boot_url,
- 'boot_device': boot_device})
- return
-
- raise exception.InvalidParameterValue(
- _('No suitable virtual media device found'))
-
- @staticmethod
- def _eject_vmedia(task, boot_device=None):
- """Eject virtual CDs and DVDs
-
- :param task: A task from TaskManager.
- :param boot_device: sushy boot device e.g. `VIRTUAL_MEDIA_CD`,
- `VIRTUAL_MEDIA_DVD` or `VIRTUAL_MEDIA_FLOPPY` or `None` to
- eject everything (default).
- :raises: InvalidParameterValue, if no suitable virtual CD or DVD is
- found on the node.
- """
- system = redfish_utils.get_system(task.node)
-
- for manager in system.managers:
- for v_media in manager.virtual_media.get_members():
- if boot_device and boot_device not in v_media.media_types:
- continue
-
- inserted = v_media.inserted
-
- if inserted:
- v_media.eject_media()
-
- LOG.info("Boot media is%(already)s ejected from "
- "%(boot_device)s for node %(node)s"
- "", {'node': task.node.uuid,
- 'already': '' if inserted else ' already',
- 'boot_device': v_media.name})
-
- @staticmethod
- def _has_vmedia_device(task, boot_device):
- """Indicate if device exists at any of the managers
-
- :param task: A task from TaskManager.
- :param boot_device: sushy boot device e.g. `VIRTUAL_MEDIA_CD`,
- `VIRTUAL_MEDIA_DVD` or `VIRTUAL_MEDIA_FLOPPY`.
- """
- system = redfish_utils.get_system(task.node)
+ _eject_vmedia(task, sushy.VIRTUAL_MEDIA_FLOPPY)
- for manager in system.managers:
- for v_media in manager.virtual_media.get_members():
- if boot_device in v_media.media_types:
- return True
+ _cleanup_iso_image(task)
@classmethod
def _set_boot_device(cls, task, device, persistent=False):