diff --git a/ironic/drivers/modules/redfish/boot.py b/ironic/drivers/modules/redfish/boot.py index f842a04fdc..43a2e9b50a 100644 --- a/ironic/drivers/modules/redfish/boot.py +++ b/ironic/drivers/modules/redfish/boot.py @@ -83,9 +83,567 @@ KERNEL_RAMDISK_LABELS = { 'rescue': RESCUE_PROPERTIES } +IMAGE_SUBDIR = 'redfish' + sushy = importutils.try_import('sushy') +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 + + mode = deploy_utils.rescue_or_deploy_mode(node) + params_to_check = KERNEL_RAMDISK_LABELS[mode] + + deploy_info = {option: d_info.get(option) + for option in params_to_check} + + if not any(deploy_info.values()): + # NOTE(dtantsur): avoid situation when e.g. deploy_kernel comes + # from driver_info but deploy_ramdisk comes from configuration, + # since it's a sign of a potential operator's mistake. + deploy_info = {k: getattr(CONF.conductor, k) + for k in params_to_check} + + 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) + + deploy_info.update( + {option: d_info.get(option, getattr(CONF.conductor, option, None)) + for option in OPTIONAL_PROPERTIES}) + + deploy_info.update(redfish_utils.parse_driver_info(node)) + + return deploy_info + + +def _parse_instance_info(node): + """Gets the instance specific Node deployment info. + + 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. + + :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() + + # NOTE(etingof): this method is currently no-op, here for completeness + return deploy_info + + +def _append_filename_param(url, filename): + """Append 'filename=' parameter to given URL. + + 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. + + However, `url` with fragments might not get cured by this hack. + + :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 + + parsed_qs.append(('filename', filename)) + parsed_url = list(parsed_url) + parsed_url[4] = urlparse.urlencode(parsed_qs) + + return urlparse.urlunparse(parsed_url) + + +def _get_floppy_image_name(node): + """Returns the floppy image name for a given node. + + :param node: the node for which image name is to be provided. + """ + return "image-%s" % node.uuid + + +def _get_iso_image_name(node): + """Returns the boot iso image name for a given node. + + :param node: the node for which image name is to be provided. + """ + return "boot-%s" % node.uuid + + +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')) + + +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}) + + +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) + + for manager in system.managers: + for v_media in manager.virtual_media.get_members(): + if boot_device in v_media.media_types: + return True + + +def _cleanup_iso_image(task): + """Deletes the ISO if it was created for the instance. + + :param task: A task from TaskManager. + """ + iso_object_name = _get_iso_image_name(task.node) + + _unpublish_image(iso_object_name) + + +def _unpublish_image(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. + + :param object_name: name of the published file (optional) + """ + if CONF.redfish.use_swift: + container = CONF.redfish.swift_container + + swift_api = swift.SwiftAPI() + + 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) + + LOG.debug("Trying to create floppy image for node " + "%(node)s", {'node': task.node.uuid}) + + with tempfile.NamedTemporaryFile( + dir=CONF.tempdir, suffix='.img') as vfat_image_tmpfile_obj: + + vfat_image_tmpfile = vfat_image_tmpfile_obj.name + images.create_vfat_image(vfat_image_tmpfile, parameters=params) + + image_url = _publish_image(vfat_image_tmpfile, object_name) + + 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}) + + return image_url + + +def _publish_image(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. + + :param image_file: path to file to publish + :param object_name: name of the published file + :return: a URL to download published file + """ + + if CONF.redfish.use_swift: + container = CONF.redfish.swift_container + timeout = CONF.redfish.swift_object_expiry_timeout + + object_headers = {'X-Delete-After': str(timeout)} + + swift_api = swift.SwiftAPI() + + swift_api.create_object(container, object_name, image_file, + object_headers=object_headers) + + image_url = swift_api.get_temp_url(container, object_name, timeout) + + else: + public_dir = os.path.join(CONF.deploy.http_root, IMAGE_SUBDIR) + + if not os.path.exists(public_dir): + os.mkdir(public_dir, 0x755) + + published_file = os.path.join(public_dir, object_name) + + 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}) + + shutil.copyfile(image_file, published_file) + + image_url = os.path.join( + CONF.deploy.http_url, IMAGE_SUBDIR, object_name) + + image_url = _append_filename_param( + image_url, os.path.basename(image_file)) + + return image_url + + +def _cleanup_floppy_image(task): + """Deletes the floppy image if it was created for the node. + + :param task: an ironic node object. + """ + floppy_object_name = _get_floppy_image_name(task.node) + + _unpublish_image(floppy_object_name) + + +def _parse_deploy_info(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(_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: + + 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 + + d_info = _parse_driver_info(node) + + kernel_href = d_info.get('%s_kernel' % mode) + ramdisk_href = d_info.get('%s_ramdisk' % mode) + bootloader_href = d_info.get('bootloader') + + return _prepare_iso_image( + task, kernel_href, ramdisk_href, bootloader_href, params=params) + + +def _prepare_boot_iso(task, root_uuid=None): + """Prepare boot ISO image + + 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. + + Push produced ISO image up to Glance and return temporary Swift + URL to the image. + + :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 + + d_info = _parse_deploy_info(node) + + kernel_href = node.instance_info.get('kernel') + ramdisk_href = node.instance_info.get('ramdisk') + + if not kernel_href or not ramdisk_href: + + image_href = d_info['image_source'] + + image_properties = ( + images.get_image_properties( + task.context, image_href, ['kernel_id', 'ramdisk_id'])) + + if not kernel_href: + kernel_href = image_properties.get('kernel_id') + + if not ramdisk_href: + ramdisk_href = image_properties.get('ramdisk_id') + + 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}) + + bootloader_href = d_info.get('bootloader') + + return _prepare_iso_image( + task, kernel_href, ramdisk_href, bootloader_href, + root_uuid=root_uuid) + + class RedfishVirtualMediaBoot(base.BootInterface): """Virtual media boot interface over Redfish. @@ -116,8 +674,6 @@ class RedfishVirtualMediaBoot(base.BootInterface): `[instance_info]image_source` node property. """ - IMAGE_SUBDIR = 'redfish' - capabilities = ['iscsi_volume_boot', 'ramdisk_boot'] def __init__(self): @@ -132,476 +688,6 @@ class RedfishVirtualMediaBoot(base.BootInterface): driver='redfish', reason=_('Unable to import the sushy library')) - @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 - - mode = deploy_utils.rescue_or_deploy_mode(node) - params_to_check = KERNEL_RAMDISK_LABELS[mode] - - deploy_info = {option: d_info.get(option) - for option in params_to_check} - - if not any(deploy_info.values()): - # NOTE(dtantsur): avoid situation when e.g. deploy_kernel comes - # from driver_info but deploy_ramdisk comes from configuration, - # since it's a sign of a potential operator's mistake. - deploy_info = {k: getattr(CONF.conductor, k) - for k in params_to_check} - - 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) - - deploy_info.update( - {option: d_info.get(option, getattr(CONF.conductor, option, None)) - for option in OPTIONAL_PROPERTIES}) - - deploy_info.update(redfish_utils.parse_driver_info(node)) - - return deploy_info - - @staticmethod - def _parse_instance_info(node): - """Gets the instance specific Node deployment info. - - 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. - - :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() - - # NOTE(etingof): this method is currently no-op, here for completeness - return deploy_info - - @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 - - @staticmethod - def _append_filename_param(url, filename): - """Append 'filename=' parameter to given URL. - - 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. - - However, `url` with fragments might not get cured by this hack. - - :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 - - parsed_qs.append(('filename', filename)) - parsed_url = list(parsed_url) - parsed_url[4] = urlparse.urlencode(parsed_qs) - - return urlparse.urlunparse(parsed_url) - - @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. - - :param image_file: path to file to publish - :param object_name: name of the published file - :return: a URL to download published file - """ - - if CONF.redfish.use_swift: - container = CONF.redfish.swift_container - timeout = CONF.redfish.swift_object_expiry_timeout - - object_headers = {'X-Delete-After': str(timeout)} - - swift_api = swift.SwiftAPI() - - swift_api.create_object(container, object_name, image_file, - object_headers=object_headers) - - image_url = swift_api.get_temp_url(container, object_name, timeout) - - else: - public_dir = os.path.join(CONF.deploy.http_root, cls.IMAGE_SUBDIR) - - if not os.path.exists(public_dir): - os.mkdir(public_dir, 0x755) - - published_file = os.path.join(public_dir, object_name) - - 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}) - - shutil.copyfile(image_file, published_file) - - image_url = os.path.join( - CONF.deploy.http_url, cls.IMAGE_SUBDIR, object_name) - - image_url = cls._append_filename_param( - image_url, os.path.basename(image_file)) - - return image_url - - @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. - - :param object_name: name of the published file (optional) - """ - if CONF.redfish.use_swift: - container = CONF.redfish.swift_container - - swift_api = swift.SwiftAPI() - - 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, cls.IMAGE_SUBDIR, object_name) - - ironic_utils.unlink_without_raise(published_file) - - @staticmethod - def _get_floppy_image_name(node): - """Returns the floppy image name for a given node. - - :param node: the node for which image name is to be provided. - """ - return "image-%s" % node.uuid - - @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) - - cls._unpublish_image(floppy_object_name) - - @classmethod - def _prepare_floppy_image(cls, 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 = cls._get_floppy_image_name(task.node) - - LOG.debug("Trying to create floppy image for node " - "%(node)s", {'node': task.node.uuid}) - - with tempfile.NamedTemporaryFile( - dir=CONF.tempdir, suffix='.img') as vfat_image_tmpfile_obj: - - vfat_image_tmpfile = vfat_image_tmpfile_obj.name - images.create_vfat_image(vfat_image_tmpfile, parameters=params) - - image_url = cls._publish_image(vfat_image_tmpfile, object_name) - - 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}) - - return image_url - - @staticmethod - def _get_iso_image_name(node): - """Returns the boot iso image name for a given node. - - :param node: the node for which image name is to be provided. - """ - return "boot-%s" % node.uuid - - @classmethod - def _cleanup_iso_image(cls, task): - """Deletes the ISO if it was created for the instance. - - :param task: an ironic node object. - """ - iso_object_name = cls._get_iso_image_name(task.node) - - cls._unpublish_image(iso_object_name) - - @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. - - 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: - - 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 = cls._get_iso_image_name(task.node) - - image_url = cls._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 - - @classmethod - def _prepare_deploy_iso(cls, 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 - - d_info = cls._parse_driver_info(node) - - kernel_href = d_info.get('%s_kernel' % mode) - ramdisk_href = d_info.get('%s_ramdisk' % mode) - bootloader_href = d_info.get('bootloader') - - return cls._prepare_iso_image( - task, kernel_href, ramdisk_href, bootloader_href, params=params) - - @classmethod - def _prepare_boot_iso(cls, task, root_uuid=None): - """Prepare boot ISO image - - 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. - - Push produced ISO image up to Glance and return temporary Swift - URL to the image. - - :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 - - d_info = cls._parse_deploy_info(node) - - kernel_href = node.instance_info.get('kernel') - ramdisk_href = node.instance_info.get('ramdisk') - - if not kernel_href or not ramdisk_href: - - image_href = d_info['image_source'] - - image_properties = ( - images.get_image_properties( - task.context, image_href, ['kernel_id', 'ramdisk_id'])) - - if not kernel_href: - kernel_href = image_properties.get('kernel_id') - - if not ramdisk_href: - ramdisk_href = image_properties.get('ramdisk_id') - - 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}) - - bootloader_href = d_info.get('bootloader') - - return cls._prepare_iso_image( - task, kernel_href, ramdisk_href, bootloader_href, - root_uuid=root_uuid) - 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) + _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) - - 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 c1d86486a7..5558996ca7 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 = redfish_boot._publish_image('file.iso', 'boot.iso') - url = task.driver.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 = redfish_boot._publish_image('file.iso', 'boot.iso') - url = task.driver.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)