diff options
author | Iury Gregory Melo Ferreira <imelofer@redhat.com> | 2020-04-30 16:54:16 +0200 |
---|---|---|
committer | Iury Gregory Melo Ferreira <imelofer@redhat.com> | 2020-04-30 18:58:30 +0200 |
commit | 2ffbfb4b4627f05954ce453436982fca1448af9a (patch) | |
tree | 247d787e4c8ef02476907e9acd0bb7e0b2eeb5cf | |
parent | 2afbd77c12d90925f24555fa4e36e28b2bc6e6a6 (diff) | |
download | ironic-2ffbfb4b4627f05954ce453436982fca1448af9a.tar.gz |
Refactor RedfishVirtualMediaBoot
- Move staticmethods and classmethods to functions, since
in py36 we have a bug when ussing unittest.mock [1]
The only classmethod we are keeping is `_set_boot_device`
since it's used by `DracRedfishVirtualMediaBoot`
[1] https://bugs.python.org/issue23078
Change-Id: I6e51bb956890a01535c62f8a378756e65d438ac3
-rw-r--r-- | ironic/drivers/modules/redfish/boot.py | 1033 | ||||
-rw-r--r-- | ironic/tests/unit/drivers/modules/redfish/test_boot.py | 248 |
2 files changed, 609 insertions, 672 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): diff --git a/ironic/tests/unit/drivers/modules/redfish/test_boot.py b/ironic/tests/unit/drivers/modules/redfish/test_boot.py index c1d86486a..5558996ca 100644 --- a/ironic/tests/unit/drivers/modules/redfish/test_boot.py +++ b/ironic/tests/unit/drivers/modules/redfish/test_boot.py @@ -67,7 +67,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): 'bootloader': 'bootloader'} ) - actual_driver_info = task.driver.boot._parse_driver_info(task.node) + actual_driver_info = redfish_boot._parse_driver_info(task.node) self.assertIn('kernel', actual_driver_info['deploy_kernel']) self.assertIn('ramdisk', actual_driver_info['deploy_ramdisk']) @@ -83,7 +83,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): 'bootloader': 'bootloader'} ) - actual_driver_info = task.driver.boot._parse_driver_info(task.node) + actual_driver_info = redfish_boot._parse_driver_info(task.node) self.assertIn('kernel', actual_driver_info['rescue_kernel']) self.assertIn('ramdisk', actual_driver_info['rescue_ramdisk']) @@ -93,7 +93,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: self.assertRaises(exception.MissingParameterValue, - task.driver.boot._parse_driver_info, + redfish_boot._parse_driver_info, task.node) def _test_parse_driver_info_from_conf(self, mode='deploy'): @@ -109,7 +109,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): self.config(group='conductor', **expected) - image_info = task.driver.boot._parse_driver_info(task.node) + image_info = redfish_boot._parse_driver_info(task.node) for key, value in expected.items(): self.assertEqual(value, image_info[key]) @@ -139,7 +139,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): task.node.driver_info.update(ramdisk_config) self.assertRaises(exception.MissingParameterValue, - task.driver.boot._parse_driver_info, task.node) + redfish_boot._parse_driver_info, task.node) def test_parse_driver_info_mixed_source_deploy(self): self._test_parse_driver_info_mixed_source() @@ -161,8 +161,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): 'kernel': 'http://kernel/img', 'ramdisk': 'http://ramdisk/img'}) - actual_instance_info = task.driver.boot._parse_deploy_info( - task.node) + actual_instance_info = redfish_boot._parse_deploy_info(task.node) self.assertEqual( 'http://boot/iso', actual_instance_info['image_source']) @@ -175,52 +174,44 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: self.assertRaises(exception.MissingParameterValue, - task.driver.boot._parse_deploy_info, + redfish_boot._parse_deploy_info, task.node) def test__append_filename_param_without_qs(self): - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - res = task.driver.boot._append_filename_param( - 'http://a.b/c', 'b.img') - expected = 'http://a.b/c?filename=b.img' - self.assertEqual(expected, res) + res = redfish_boot._append_filename_param( + 'http://a.b/c', 'b.img') + expected = 'http://a.b/c?filename=b.img' + self.assertEqual(expected, res) def test__append_filename_param_with_qs(self): - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - res = task.driver.boot._append_filename_param( - 'http://a.b/c?d=e&f=g', 'b.img') - expected = 'http://a.b/c?d=e&f=g&filename=b.img' - self.assertEqual(expected, res) + res = redfish_boot._append_filename_param( + 'http://a.b/c?d=e&f=g', 'b.img') + expected = 'http://a.b/c?d=e&f=g&filename=b.img' + self.assertEqual(expected, res) def test__append_filename_param_with_filename(self): - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - res = task.driver.boot._append_filename_param( - 'http://a.b/c?filename=bootme.img', 'b.img') - expected = 'http://a.b/c?filename=bootme.img' - self.assertEqual(expected, res) + res = redfish_boot._append_filename_param( + 'http://a.b/c?filename=bootme.img', 'b.img') + expected = 'http://a.b/c?filename=bootme.img' + self.assertEqual(expected, res) @mock.patch.object(redfish_boot, 'swift', autospec=True) def test__publish_image_swift(self, mock_swift): - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - mock_swift_api = mock_swift.SwiftAPI.return_value - mock_swift_api.get_temp_url.return_value = 'https://a.b/c.f?e=f' + mock_swift_api = mock_swift.SwiftAPI.return_value + mock_swift_api.get_temp_url.return_value = 'https://a.b/c.f?e=f' - url = task.driver.boot._publish_image('file.iso', 'boot.iso') + url = redfish_boot._publish_image('file.iso', 'boot.iso') - self.assertEqual( - 'https://a.b/c.f?e=f&filename=file.iso', url) + self.assertEqual( + 'https://a.b/c.f?e=f&filename=file.iso', url) - mock_swift.SwiftAPI.assert_called_once_with() + mock_swift.SwiftAPI.assert_called_once_with() - mock_swift_api.create_object.assert_called_once_with( - mock.ANY, mock.ANY, mock.ANY, mock.ANY) + mock_swift_api.create_object.assert_called_once_with( + mock.ANY, mock.ANY, mock.ANY, mock.ANY) - mock_swift_api.get_temp_url.assert_called_once_with( - mock.ANY, mock.ANY, mock.ANY) + mock_swift_api.get_temp_url.assert_called_once_with( + mock.ANY, mock.ANY, mock.ANY) @mock.patch.object(redfish_boot, 'swift', autospec=True) def test__unpublish_image_swift(self, mock_swift): @@ -228,7 +219,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): shared=True) as task: object_name = 'image-%s' % task.node.uuid - task.driver.boot._unpublish_image(object_name) + redfish_boot._unpublish_image(object_name) mock_swift.SwiftAPI.assert_called_once_with() mock_swift_api = mock_swift.SwiftAPI.return_value @@ -244,17 +235,14 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): self.config(use_swift=False, group='redfish') self.config(http_url='http://localhost', group='deploy') - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - - url = task.driver.boot._publish_image('file.iso', 'boot.iso') + url = redfish_boot._publish_image('file.iso', 'boot.iso') - self.assertEqual( - 'http://localhost/redfish/boot.iso?filename=file.iso', url) + self.assertEqual( + 'http://localhost/redfish/boot.iso?filename=file.iso', url) - mock_mkdir.assert_called_once_with('/httpboot/redfish', 0x755) - mock_link.assert_called_once_with( - 'file.iso', '/httpboot/redfish/boot.iso') + mock_mkdir.assert_called_once_with('/httpboot/redfish', 0x755) + mock_link.assert_called_once_with( + 'file.iso', '/httpboot/redfish/boot.iso') @mock.patch.object(redfish_boot, 'shutil', autospec=True) @mock.patch.object(os, 'link', autospec=True) @@ -266,18 +254,15 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): mock_link.side_effect = OSError() - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - - url = task.driver.boot._publish_image('file.iso', 'boot.iso') + url = redfish_boot._publish_image('file.iso', 'boot.iso') - self.assertEqual( - 'http://localhost/redfish/boot.iso?filename=file.iso', url) + self.assertEqual( + 'http://localhost/redfish/boot.iso?filename=file.iso', url) - mock_mkdir.assert_called_once_with('/httpboot/redfish', 0x755) + mock_mkdir.assert_called_once_with('/httpboot/redfish', 0x755) - mock_shutil.copyfile.assert_called_once_with( - 'file.iso', '/httpboot/redfish/boot.iso') + mock_shutil.copyfile.assert_called_once_with( + 'file.iso', '/httpboot/redfish/boot.iso') @mock.patch.object(redfish_boot, 'ironic_utils', autospec=True) def test__unpublish_image_local(self, mock_ironic_utils): @@ -289,24 +274,22 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): expected_file = '/httpboot/redfish/' + object_name - task.driver.boot._unpublish_image(object_name) + redfish_boot._unpublish_image(object_name) mock_ironic_utils.unlink_without_raise.assert_called_once_with( expected_file) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_unpublish_image', autospec=True) + @mock.patch.object(redfish_boot, '_unpublish_image', autospec=True) def test__cleanup_floppy_image(self, mock_unpublish): with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: - task.driver.boot._cleanup_floppy_image(task) + redfish_boot._cleanup_floppy_image(task) object_name = 'image-%s' % task.node.uuid mock_unpublish.assert_called_once_with(object_name) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_publish_image', autospec=True) + @mock.patch.object(redfish_boot, '_publish_image', autospec=True) @mock.patch.object(images, 'create_vfat_image', autospec=True) def test__prepare_floppy_image( self, mock_create_vfat_image, mock__publish_image): @@ -316,7 +299,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): mock__publish_image.return_value = expected_url - url = task.driver.boot._prepare_floppy_image(task) + url = redfish_boot._prepare_floppy_image(task) object_name = 'image-%s' % task.node.uuid @@ -328,19 +311,17 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): self.assertEqual(expected_url, url) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_unpublish_image', autospec=True) + @mock.patch.object(redfish_boot, '_unpublish_image', autospec=True) def test__cleanup_iso_image(self, mock_unpublish): with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: - task.driver.boot._cleanup_iso_image(task) + redfish_boot._cleanup_iso_image(task) object_name = 'boot-%s' % task.node.uuid mock_unpublish.assert_called_once_with(object_name) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_publish_image', autospec=True) + @mock.patch.object(redfish_boot, '_publish_image', autospec=True) @mock.patch.object(images, 'create_boot_iso', autospec=True) def test__prepare_iso_image_uefi( self, mock_create_boot_iso, mock__publish_image): @@ -352,7 +333,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): mock__publish_image.return_value = expected_url - url = task.driver.boot._prepare_iso_image( + url = redfish_boot._prepare_iso_image( task, 'http://kernel/img', 'http://ramdisk/img', 'http://bootloader/img', root_uuid=task.node.uuid) @@ -370,8 +351,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): self.assertEqual(expected_url, url) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_publish_image', autospec=True) + @mock.patch.object(redfish_boot, '_publish_image', autospec=True) @mock.patch.object(images, 'create_boot_iso', autospec=True) def test__prepare_iso_image_bios( self, mock_create_boot_iso, mock__publish_image): @@ -382,7 +362,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): mock__publish_image.return_value = expected_url - url = task.driver.boot._prepare_iso_image( + url = redfish_boot._prepare_iso_image( task, 'http://kernel/img', 'http://ramdisk/img', bootloader_href=None, root_uuid=task.node.uuid) @@ -400,8 +380,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): self.assertEqual(expected_url, url) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_publish_image', autospec=True) + @mock.patch.object(redfish_boot, '_publish_image', autospec=True) @mock.patch.object(images, 'create_boot_iso', autospec=True) def test__prepare_iso_image_kernel_params( self, mock_create_boot_iso, mock__publish_image): @@ -411,7 +390,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): task.node.instance_info.update(kernel_append_params=kernel_params) - task.driver.boot._prepare_iso_image( + redfish_boot._prepare_iso_image( task, 'http://kernel/img', 'http://ramdisk/img', bootloader_href=None, root_uuid=task.node.uuid) @@ -422,8 +401,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): kernel_params=kernel_params, root_uuid='1be26c0b-03f2-4d2e-ae87-c02d7f33c123') - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_prepare_iso_image', autospec=True) + @mock.patch.object(redfish_boot, '_prepare_iso_image', autospec=True) def test__prepare_deploy_iso(self, mock__prepare_iso_image): with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: @@ -436,13 +414,12 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): task.node.instance_info.update(deploy_boot_mode='uefi') - task.driver.boot._prepare_deploy_iso(task, {}, 'deploy') + redfish_boot._prepare_deploy_iso(task, {}, 'deploy') mock__prepare_iso_image.assert_called_once_with( mock.ANY, 'kernel', 'ramdisk', 'bootloader', params={}) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_prepare_iso_image', autospec=True) + @mock.patch.object(redfish_boot, '_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): @@ -459,7 +436,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): 'kernel': 'http://kernel/img', 'ramdisk': 'http://ramdisk/img'}) - task.driver.boot._prepare_boot_iso( + redfish_boot._prepare_boot_iso( task, root_uuid=task.node.uuid) mock__prepare_iso_image.assert_called_once_with( @@ -557,14 +534,10 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): @mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_prepare_deploy_iso', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_eject_vmedia', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_insert_vmedia', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_parse_driver_info', autospec=True) + @mock.patch.object(redfish_boot, '_prepare_deploy_iso', autospec=True) + @mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True) + @mock.patch.object(redfish_boot, '_insert_vmedia', autospec=True) + @mock.patch.object(redfish_boot, '_parse_driver_info', autospec=True) @mock.patch.object(redfish_boot.manager_utils, 'node_power_action', autospec=True) @mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True) @@ -607,14 +580,10 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): @mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_prepare_deploy_iso', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_eject_vmedia', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_insert_vmedia', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_parse_driver_info', autospec=True) + @mock.patch.object(redfish_boot, '_prepare_deploy_iso', autospec=True) + @mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True) + @mock.patch.object(redfish_boot, '_insert_vmedia', autospec=True) + @mock.patch.object(redfish_boot, '_parse_driver_info', autospec=True) @mock.patch.object(redfish_boot.manager_utils, 'node_power_action', autospec=True) @mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True) @@ -656,18 +625,12 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): @mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_prepare_floppy_image', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_prepare_deploy_iso', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_has_vmedia_device', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_eject_vmedia', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_insert_vmedia', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_parse_driver_info', autospec=True) + @mock.patch.object(redfish_boot, '_prepare_floppy_image', autospec=True) + @mock.patch.object(redfish_boot, '_prepare_deploy_iso', autospec=True) + @mock.patch.object(redfish_boot, '_has_vmedia_device', autospec=True) + @mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True) + @mock.patch.object(redfish_boot, '_insert_vmedia', autospec=True) + @mock.patch.object(redfish_boot, '_parse_driver_info', autospec=True) @mock.patch.object(redfish_boot.manager_utils, 'node_power_action', autospec=True) @mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True) @@ -728,16 +691,11 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_has_vmedia_device', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_eject_vmedia', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_cleanup_iso_image', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_cleanup_floppy_image', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_parse_driver_info', autospec=True) + @mock.patch.object(redfish_boot, '_has_vmedia_device', autospec=True) + @mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True) + @mock.patch.object(redfish_boot, '_cleanup_iso_image', autospec=True) + @mock.patch.object(redfish_boot, '_cleanup_floppy_image', autospec=True) + @mock.patch.object(redfish_boot, '_parse_driver_info', autospec=True) def test_clean_up_ramdisk( self, mock__parse_driver_info, mock__cleanup_floppy_image, mock__cleanup_iso_image, mock__eject_vmedia, @@ -768,14 +726,10 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, 'clean_up_instance', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_prepare_boot_iso', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_eject_vmedia', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_insert_vmedia', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_parse_driver_info', autospec=True) + @mock.patch.object(redfish_boot, '_prepare_boot_iso', autospec=True) + @mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True) + @mock.patch.object(redfish_boot, '_insert_vmedia', autospec=True) + @mock.patch.object(redfish_boot, '_parse_driver_info', autospec=True) @mock.patch.object(redfish_boot, 'manager_utils', autospec=True) @mock.patch.object(redfish_boot, 'deploy_utils', autospec=True) @mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True) @@ -817,14 +771,10 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, 'clean_up_instance', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_prepare_boot_iso', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_eject_vmedia', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_insert_vmedia', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_parse_driver_info', autospec=True) + @mock.patch.object(redfish_boot, '_prepare_boot_iso', autospec=True) + @mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True) + @mock.patch.object(redfish_boot, '_insert_vmedia', autospec=True) + @mock.patch.object(redfish_boot, '_parse_driver_info', autospec=True) @mock.patch.object(redfish_boot, 'manager_utils', autospec=True) @mock.patch.object(redfish_boot, 'deploy_utils', autospec=True) @mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True) @@ -858,10 +808,8 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_eject_vmedia', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_cleanup_iso_image', autospec=True) + @mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True) + @mock.patch.object(redfish_boot, '_cleanup_iso_image', autospec=True) @mock.patch.object(redfish_boot, 'manager_utils', autospec=True) def _test_prepare_instance_local_boot( self, mock_manager_utils, @@ -893,10 +841,8 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): self.node.save() self._test_prepare_instance_local_boot() - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_eject_vmedia', autospec=True) - @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, - '_cleanup_iso_image', autospec=True) + @mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True) + @mock.patch.object(redfish_boot, '_cleanup_iso_image', autospec=True) def _test_clean_up_instance(self, mock__cleanup_iso_image, mock__eject_vmedia): @@ -942,7 +888,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): mock_redfish_utils.get_system.return_value.managers = [ mock_manager] - task.driver.boot._insert_vmedia( + redfish_boot._insert_vmedia( task, 'img-url', sushy.VIRTUAL_MEDIA_CD) mock_vmedia_cd.insert_media.assert_called_once_with( @@ -967,7 +913,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): mock_redfish_utils.get_system.return_value.managers = [ mock_manager] - task.driver.boot._insert_vmedia( + redfish_boot._insert_vmedia( task, 'img-url', sushy.VIRTUAL_MEDIA_CD) self.assertFalse(mock_vmedia_cd.insert_media.call_count) @@ -990,7 +936,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): self.assertRaises( exception.InvalidParameterValue, - task.driver.boot._insert_vmedia, + redfish_boot._insert_vmedia, task, 'img-url', sushy.VIRTUAL_MEDIA_CD) @mock.patch.object(redfish_boot, 'redfish_utils', autospec=True) @@ -1013,7 +959,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): mock_redfish_utils.get_system.return_value.managers = [ mock_manager] - task.driver.boot._eject_vmedia(task) + redfish_boot._eject_vmedia(task) mock_vmedia_cd.eject_media.assert_called_once_with() mock_vmedia_floppy.eject_media.assert_called_once_with() @@ -1038,7 +984,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): mock_redfish_utils.get_system.return_value.managers = [ mock_manager] - task.driver.boot._eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD) + redfish_boot._eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD) mock_vmedia_cd.eject_media.assert_called_once_with() self.assertFalse(mock_vmedia_floppy.eject_media.call_count) @@ -1063,7 +1009,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): mock_redfish_utils.get_system.return_value.managers = [ mock_manager] - task.driver.boot._eject_vmedia(task) + redfish_boot._eject_vmedia(task) self.assertFalse(mock_vmedia_cd.eject_media.call_count) self.assertFalse(mock_vmedia_floppy.eject_media.call_count) @@ -1085,6 +1031,6 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase): mock_redfish_utils.get_system.return_value.managers = [ mock_manager] - task.driver.boot._eject_vmedia(task) + redfish_boot._eject_vmedia(task) self.assertFalse(mock_vmedia_cd.eject_media.call_count) |