Merge "Generalize ISO building for virtual media driver"

This commit is contained in:
Zuul 2020-04-17 12:32:55 +00:00 committed by Gerrit Code Review
commit 8a143378cb
13 changed files with 703 additions and 707 deletions

View File

@ -16,7 +16,9 @@
import os import os
import re import re
import shutil
import time import time
from urllib import parse as urlparse
from ironic_lib import metrics_utils from ironic_lib import metrics_utils
from ironic_lib import utils as il_utils from ironic_lib import utils as il_utils
@ -824,6 +826,35 @@ def direct_deploy_should_convert_raw_image(node):
return CONF.force_raw_images and CONF.agent.stream_raw_images return CONF.force_raw_images and CONF.agent.stream_raw_images
def copy_image_to_web_server(source_file_path, destination):
"""Copies the given image to the http web server.
This method copies the given image to the http_root location.
It enables read-write access to the image else the deploy fails
as the image file at the web_server url is inaccessible.
:param source_file_path: The absolute path of the image file
which needs to be copied to the
web server root.
:param destination: The name of the file that
will contain the copied image.
:raises: ImageUploadFailed exception if copying the source
file to the web server fails.
:returns: image url after the source image is uploaded.
"""
image_url = urlparse.urljoin(CONF.deploy.http_url, destination)
image_path = os.path.join(CONF.deploy.http_root, destination)
try:
shutil.copyfile(source_file_path, image_path)
except IOError as exc:
raise exception.ImageUploadFailed(image_name=destination,
web_server=CONF.deploy.http_url,
reason=exc)
return image_url
@image_cache.cleanup(priority=50) @image_cache.cleanup(priority=50)
class InstanceImageCache(image_cache.ImageCache): class InstanceImageCache(image_cache.ImageCache):

View File

@ -16,8 +16,6 @@ Boot Interface for iLO drivers and its supporting methods.
""" """
import os import os
import tempfile
from urllib import parse as urlparse
from ironic_lib import metrics_utils from ironic_lib import metrics_utils
from ironic_lib import utils as ironic_utils from ironic_lib import utils as ironic_utils
@ -40,6 +38,7 @@ from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.ilo import common as ilo_common from ironic.drivers.modules.ilo import common as ilo_common
from ironic.drivers.modules import ipxe from ironic.drivers.modules import ipxe
from ironic.drivers.modules import pxe from ironic.drivers.modules import pxe
from ironic.drivers.modules import virtual_media_base
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -47,16 +46,45 @@ METRICS = metrics_utils.get_metrics_logger(__name__)
CONF = cfg.CONF CONF = cfg.CONF
REQUIRED_PROPERTIES = { DEPLOY_ISO_PROPERTIES = {
'ilo_deploy_iso': _("UUID (from Glance) of the deployment ISO. " 'ilo_deploy_iso': _("UUID (from Glance) of the deployment ISO. "
"Required.") "Required.")
} }
RESCUE_PROPERTIES = {
DEPLOY_RAMDISK_PROPERTIES = {
'deploy_kernel': _("URL or Glance UUID of the deployment kernel. "
"Required."),
'deploy_ramdisk': _("URL or Glance UUID of the ramdisk that is "
"mounted at boot time. Required.")
}
OPTIONAL_PROPERTIES = {
'bootloader': _("URL or Glance UUID of the EFI system partition "
"image containing EFI boot loader. This image will be "
"used by ironic when building UEFI-bootable ISO "
"out of kernel and ramdisk. Required for UEFI "
"boot from partition images.")
}
RESCUE_ISO_PROPERTIES = {
'ilo_rescue_iso': _("UUID (from Glance) of the rescue ISO. Only " 'ilo_rescue_iso': _("UUID (from Glance) of the rescue ISO. Only "
"required if rescue mode is being used and ironic is " "required if rescue mode is being used and ironic is "
"managing booting the rescue ramdisk.") "managing booting the rescue ramdisk.")
} }
COMMON_PROPERTIES = REQUIRED_PROPERTIES
RESCUE_RAMDISK_PROPERTIES = {
'rescue_kernel': _("URL or Glance UUID of the rescue kernel. "
"Required."),
'rescue_ramdisk': _("URL or Glance UUID of the ramdisk that is "
"mounted at boot time. Required.")
}
COMMON_PROPERTIES = DEPLOY_ISO_PROPERTIES
KERNEL_RAMDISK_LABELS = {
'deploy': DEPLOY_RAMDISK_PROPERTIES,
'rescue': RESCUE_RAMDISK_PROPERTIES
}
def parse_driver_info(node, mode='deploy'): def parse_driver_info(node, mode='deploy'):
@ -77,26 +105,37 @@ def parse_driver_info(node, mode='deploy'):
""" """
info = node.driver_info info = node.driver_info
d_info = {} d_info = {}
if mode == 'rescue': if mode == 'rescue' and info.get('ilo_rescue_iso'):
d_info['ilo_rescue_iso'] = info.get('ilo_rescue_iso') d_info['ilo_rescue_iso'] = info.get('ilo_rescue_iso')
else: elif mode == 'deploy' and info.get('ilo_deploy_iso'):
d_info['ilo_deploy_iso'] = info.get('ilo_deploy_iso') d_info['ilo_deploy_iso'] = info.get('ilo_deploy_iso')
else:
params_to_check = KERNEL_RAMDISK_LABELS[mode]
d_info = {option: info.get(option)
for option in params_to_check}
if not any(d_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.
d_info = {k: getattr(CONF.conductor, k)
for k in params_to_check}
error_msg = (_("Error validating iLO virtual media for %(mode)s. "
"Either 'ilo_%(mode)s_iso' is missing or "
"DEPLOY_RAMDISK_PROPERTIES or RESCUE_RAMDISK_PROPERTIES "
"were missing in node's driver_info.") % {'mode': mode})
error_msg = (_("Error validating iLO virtual media for %s. Some "
"parameters were missing in node's driver_info") % mode)
deploy_utils.check_for_missing_params(d_info, error_msg) deploy_utils.check_for_missing_params(d_info, error_msg)
d_info.update(
{option: info.get(option, getattr(CONF.conductor, option, None))
for option in OPTIONAL_PROPERTIES})
return d_info return d_info
def _get_boot_iso_object_name(node):
"""Returns the boot iso object name for a given node.
:param node: the node for which object name is to be provided.
"""
return "boot-%s" % node.uuid
def _get_boot_iso(task, root_uuid): def _get_boot_iso(task, root_uuid):
"""This method returns a boot ISO to boot the node. """This method returns a boot ISO to boot the node.
@ -171,12 +210,6 @@ def _get_boot_iso(task, root_uuid):
LOG.debug("Found boot_iso %s in Glance", boot_iso_uuid) LOG.debug("Found boot_iso %s in Glance", boot_iso_uuid)
return boot_iso_uuid return boot_iso_uuid
if not kernel_href or not ramdisk_href:
LOG.error("Unable to find kernel or ramdisk for "
"image %(image)s to generate boot ISO for %(node)s",
{'image': image_href, 'node': task.node.uuid})
return
# NOTE(rameshg87): Functionality to share the boot ISOs created for # NOTE(rameshg87): Functionality to share the boot ISOs created for
# similar instances (instances with same deployed image) is # similar instances (instances with same deployed image) is
# not implemented as of now. Creation/Deletion of such a shared boot ISO # not implemented as of now. Creation/Deletion of such a shared boot ISO
@ -186,43 +219,15 @@ def _get_boot_iso(task, root_uuid):
# Option 3 - Create boot_iso from kernel/ramdisk, upload to Swift # Option 3 - Create boot_iso from kernel/ramdisk, upload to Swift
# or web server and provide its name. # or web server and provide its name.
deploy_iso_uuid = deploy_info['ilo_deploy_iso'] deploy_iso_uuid = deploy_info['ilo_deploy_iso']
boot_mode = boot_mode_utils.get_boot_mode(task.node)
boot_iso_object_name = _get_boot_iso_object_name(task.node)
kernel_params = ""
if deploy_utils.get_boot_option(task.node) == "ramdisk":
i_info = task.node.instance_info
kernel_params = "root=/dev/ram0 text "
kernel_params += i_info.get("ramdisk_kernel_arguments", "")
else:
kernel_params = CONF.pxe.pxe_append_params
with tempfile.NamedTemporaryFile(dir=CONF.tempdir) as fileobj:
boot_iso_tmp_file = fileobj.name
images.create_boot_iso(task.context, boot_iso_tmp_file,
kernel_href, ramdisk_href,
deploy_iso_href=deploy_iso_uuid,
root_uuid=root_uuid,
kernel_params=kernel_params,
boot_mode=boot_mode)
if CONF.ilo.use_web_server_for_images: use_web_server = CONF.ilo.use_web_server_for_images
boot_iso_url = (
ilo_common.copy_image_to_web_server(boot_iso_tmp_file,
boot_iso_object_name))
driver_internal_info = task.node.driver_internal_info
driver_internal_info['boot_iso_created_in_web_server'] = True
task.node.driver_internal_info = driver_internal_info
task.node.save()
LOG.debug("Created boot_iso %(boot_iso)s for node %(node)s",
{'boot_iso': boot_iso_url, 'node': task.node.uuid})
return boot_iso_url
else:
container = CONF.ilo.swift_ilo_container container = CONF.ilo.swift_ilo_container
swift_api = swift.SwiftAPI()
swift_api.create_object(container, boot_iso_object_name,
boot_iso_tmp_file)
LOG.debug("Created boot_iso %s in Swift", boot_iso_object_name) return virtual_media_base.prepare_iso_image(
return 'swift:%s' % boot_iso_object_name task, kernel_href, ramdisk_href,
deploy_iso_href=deploy_iso_uuid,
root_uuid=root_uuid, use_web_server=use_web_server,
container=container)
def _clean_up_boot_iso_for_instance(node): def _clean_up_boot_iso_for_instance(node):
@ -230,25 +235,21 @@ def _clean_up_boot_iso_for_instance(node):
:param node: an ironic node object. :param node: an ironic node object.
""" """
ilo_boot_iso = node.instance_info.get('ilo_boot_iso') boot_iso_object_name = virtual_media_base.get_iso_image_name(node)
if not ilo_boot_iso:
return if CONF.ilo.use_web_server_for_images:
if ilo_boot_iso.startswith('swift'): boot_iso_path = os.path.join(
CONF.deploy.http_root, boot_iso_object_name)
ironic_utils.unlink_without_raise(boot_iso_path)
else:
swift_api = swift.SwiftAPI() swift_api = swift.SwiftAPI()
container = CONF.ilo.swift_ilo_container container = CONF.ilo.swift_ilo_container
boot_iso_object_name = _get_boot_iso_object_name(node)
try: try:
swift_api.delete_object(container, boot_iso_object_name) swift_api.delete_object(container, boot_iso_object_name)
except exception.SwiftOperationError as e: except exception.SwiftOperationError as e:
LOG.exception("Failed to clean up boot ISO for node " LOG.exception("Failed to clean up boot ISO for node "
"%(node)s. Error: %(error)s.", "%(node)s. Error: %(error)s.",
{'node': node.uuid, 'error': e}) {'node': node.uuid, 'error': e})
elif CONF.ilo.use_web_server_for_images:
result = urlparse.urlparse(ilo_boot_iso)
ilo_boot_iso_name = os.path.basename(result.path)
boot_iso_path = os.path.join(
CONF.deploy.http_root, ilo_boot_iso_name)
ironic_utils.unlink_without_raise(boot_iso_path)
def _parse_deploy_info(node): def _parse_deploy_info(node):
@ -284,19 +285,7 @@ def _validate_driver_info(task):
""" """
node = task.node node = task.node
ilo_common.parse_driver_info(node) ilo_common.parse_driver_info(node)
if 'ilo_deploy_iso' not in node.driver_info: parse_driver_info(node)
raise exception.MissingParameterValue(_(
"Missing 'ilo_deploy_iso' parameter in node's 'driver_info'."))
deploy_iso = node.driver_info['ilo_deploy_iso']
if not service_utils.is_glance_image(deploy_iso):
try:
image_service.HttpImageService().validate_href(deploy_iso)
except exception.ImageRefValidationFailed:
raise exception.InvalidParameterValue(_(
"Virtual media boot accepts only Glance images or "
"HTTP(S) as driver_info['ilo_deploy_iso']. Either '%s' "
"is not a glance UUID or not a valid HTTP(S) URL or "
"the given URL is not reachable.") % deploy_iso)
def _validate_instance_image_info(task): def _validate_instance_image_info(task):
@ -518,11 +507,19 @@ class IloVirtualMediaBoot(base.BootInterface):
deploy_nic_mac = deploy_utils.get_single_nic_with_vif_port_id(task) deploy_nic_mac = deploy_utils.get_single_nic_with_vif_port_id(task)
ramdisk_params['BOOTIF'] = deploy_nic_mac ramdisk_params['BOOTIF'] = deploy_nic_mac
if node.provision_state == states.RESCUING: if (node.driver_info.get('ilo_rescue_iso')
and node.provision_state == states.RESCUING):
iso = node.driver_info['ilo_rescue_iso'] iso = node.driver_info['ilo_rescue_iso']
else: elif node.driver_info.get('ilo_deploy_iso'):
iso = node.driver_info['ilo_deploy_iso'] iso = node.driver_info['ilo_deploy_iso']
else:
mode = deploy_utils.rescue_or_deploy_mode(node)
d_info = parse_driver_info(node, mode)
use_web_server = CONF.ilo.use_web_server_for_images
container = CONF.ilo.swift_ilo_container
iso = virtual_media_base.prepare_deploy_iso(
task, ramdisk_params, mode, d_info,
use_web_server=use_web_server, container=container)
ilo_common.setup_vmedia(task, iso, ramdisk_params) ilo_common.setup_vmedia(task, iso, ramdisk_params)
@METRICS.timer('IloVirtualMediaBoot.prepare_instance') @METRICS.timer('IloVirtualMediaBoot.prepare_instance')
@ -636,6 +633,10 @@ class IloVirtualMediaBoot(base.BootInterface):
:returns: None :returns: None
:raises: IloOperationError, if some operation on iLO failed. :raises: IloOperationError, if some operation on iLO failed.
""" """
info = task.node.driver_info
if not info.get('ilo_deploy_iso') and not info.get('ilo_rescue_iso'):
_clean_up_boot_iso_for_instance(task.node)
ilo_common.cleanup_vmedia_boot(task) ilo_common.cleanup_vmedia_boot(task)
def _configure_vmedia_boot(self, task, root_uuid): def _configure_vmedia_boot(self, task, root_uuid):

View File

@ -17,7 +17,6 @@ Common functionalities shared between different iLO modules.
""" """
import os import os
import shutil
import tempfile import tempfile
from urllib import parse as urlparse from urllib import parse as urlparse
@ -134,17 +133,12 @@ def copy_image_to_web_server(source_file_path, destination):
:returns: image url after the source image is uploaded. :returns: image url after the source image is uploaded.
""" """
LOG.warning(
'This method is obsolete and will be removed in next release. '
'Please use deploy_utils.copy_image_to_web_server() instead.')
image_url = urlparse.urljoin(CONF.deploy.http_url, destination) return deploy_utils.copy_image_to_web_server(source_file_path,
image_path = os.path.join(CONF.deploy.http_root, destination) destination)
try:
shutil.copyfile(source_file_path, image_path)
except IOError as exc:
raise exception.ImageUploadFailed(image_name=destination,
web_server=CONF.deploy.http_url,
reason=exc)
os.chmod(image_path, 0o644)
return image_url
def remove_image_from_web_server(object_name): def remove_image_from_web_server(object_name):
@ -420,8 +414,8 @@ def _prepare_floppy_image(task, params):
images.create_vfat_image(vfat_image_tmpfile, parameters=params) images.create_vfat_image(vfat_image_tmpfile, parameters=params)
object_name = _get_floppy_image_name(task.node) object_name = _get_floppy_image_name(task.node)
if CONF.ilo.use_web_server_for_images: if CONF.ilo.use_web_server_for_images:
image_url = copy_image_to_web_server(vfat_image_tmpfile, image_url = deploy_utils.copy_image_to_web_server(
object_name) vfat_image_tmpfile, object_name)
else: else:
image_url = copy_image_to_swift(vfat_image_tmpfile, object_name) image_url = copy_image_to_swift(vfat_image_tmpfile, object_name)

View File

@ -32,6 +32,7 @@ from ironic.common import exception
from ironic.common.i18n import _ from ironic.common.i18n import _
from ironic.common import image_service from ironic.common import image_service
from ironic.common import swift from ironic.common import swift
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.ilo import common as ilo_common from ironic.drivers.modules.ilo import common as ilo_common
# Supported components for firmware update when invoked through manual clean # Supported components for firmware update when invoked through manual clean
@ -370,7 +371,7 @@ def _extract_fw_from_file(node, target_file):
"firmware file %(firmware_image)s on web server ...", "firmware file %(firmware_image)s on web server ...",
{'firmware_image': fw_image_location, {'firmware_image': fw_image_location,
'node': node.uuid}) 'node': node.uuid})
fw_image_uploaded_url = ilo_common.copy_image_to_web_server( fw_image_uploaded_url = deploy_utils.copy_image_to_web_server(
fw_image_location, fw_image_filename) fw_image_location, fw_image_filename)
fw_image_location_obj.fw_image_location = fw_image_uploaded_url fw_image_location_obj.fw_image_location = fw_image_uploaded_url

View File

@ -20,7 +20,6 @@ from urllib import parse as urlparse
from ironic_lib import utils as ironic_utils from ironic_lib import utils as ironic_utils
from oslo_log import log from oslo_log import log
from oslo_serialization import base64
from oslo_utils import importutils from oslo_utils import importutils
from ironic.common import boot_devices from ironic.common import boot_devices
@ -36,6 +35,7 @@ from ironic.drivers import base
from ironic.drivers.modules import boot_mode_utils from ironic.drivers.modules import boot_mode_utils
from ironic.drivers.modules import deploy_utils from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.redfish import utils as redfish_utils from ironic.drivers.modules.redfish import utils as redfish_utils
from ironic.drivers.modules import virtual_media_base
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -392,163 +392,16 @@ class RedfishVirtualMediaBoot(base.BootInterface):
return 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 @classmethod
def _cleanup_iso_image(cls, task): def _cleanup_iso_image(cls, task):
"""Deletes the ISO if it was created for the instance. """Deletes the ISO if it was created for the instance.
:param task: an ironic node object. :param task: an ironic node object.
""" """
iso_object_name = cls._get_iso_image_name(task.node) iso_object_name = virtual_media_base.get_iso_image_name(task.node)
cls._unpublish_image(iso_object_name) 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 @classmethod
def _prepare_boot_iso(cls, task, root_uuid=None): def _prepare_boot_iso(cls, task, root_uuid=None):
"""Prepare boot ISO image """Prepare boot ISO image
@ -598,9 +451,19 @@ class RedfishVirtualMediaBoot(base.BootInterface):
bootloader_href = d_info.get('bootloader') bootloader_href = d_info.get('bootloader')
return cls._prepare_iso_image( if CONF.redfish.use_swift:
task, kernel_href, ramdisk_href, bootloader_href, use_web_server = False
root_uuid=root_uuid) container = CONF.redfish.swift_container
timeout = CONF.redfish.swift_object_expiry_timeout
else:
use_web_server = True
container = None
timeout = None
return virtual_media_base.prepare_iso_image(
task, kernel_href, ramdisk_href, bootloader_href=bootloader_href,
root_uuid=root_uuid, timeout=timeout,
use_web_server=use_web_server, container=container)
def get_properties(self): def get_properties(self):
"""Return the properties of the interface. """Return the properties of the interface.
@ -753,7 +616,16 @@ class RedfishVirtualMediaBoot(base.BootInterface):
mode = deploy_utils.rescue_or_deploy_mode(node) mode = deploy_utils.rescue_or_deploy_mode(node)
iso_ref = self._prepare_deploy_iso(task, ramdisk_params, mode) if CONF.redfish.use_swift:
use_web_server = False
container = CONF.redfish.swift_container
else:
use_web_server = True
container = None
iso_ref = virtual_media_base.prepare_deploy_iso(
task, ramdisk_params, mode, d_info,
use_web_server=use_web_server, container=container)
self._eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD) self._eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
self._insert_vmedia(task, iso_ref, sushy.VIRTUAL_MEDIA_CD) self._insert_vmedia(task, iso_ref, sushy.VIRTUAL_MEDIA_CD)

View File

@ -0,0 +1,173 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import tempfile
from oslo_log import log
from ironic.common import exception
from ironic.common.i18n import _
from ironic.common import images
from ironic.common import swift
from ironic.conf import CONF
from ironic.drivers.modules import boot_mode_utils
from ironic.drivers.modules import deploy_utils
LOG = log.getLogger(__name__)
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 prepare_iso_image(task, kernel_href, ramdisk_href, deploy_iso_href=None,
bootloader_href=None, root_uuid=None,
kernel_params=None, timeout=None,
use_web_server=False, container=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 root_uuid: optional uuid of the root partition.
:param kernel_params: a dictionary containing 'parameter name'->'value'
mapping to be passed to kernel command line.
:param timeout: swift object expiry timeout
: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})
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 fileobj:
boot_iso_tmp_file = fileobj.name
images.create_boot_iso(task.context, boot_iso_tmp_file,
kernel_href, ramdisk_href,
deploy_iso_href=deploy_iso_href,
esp_image_href=bootloader_href,
root_uuid=root_uuid,
kernel_params=kernel_params,
boot_mode=boot_mode)
iso_object_name = get_iso_image_name(task.node)
if use_web_server:
boot_iso_url = (
deploy_utils.copy_image_to_web_server(boot_iso_tmp_file,
iso_object_name))
driver_internal_info = task.node.driver_internal_info
driver_internal_info['boot_iso_created_in_web_server'] = True
task.node.driver_internal_info = driver_internal_info
task.node.save()
LOG.debug("Created boot_iso %(boot_iso)s for node %(node)s",
{'boot_iso': boot_iso_url, 'node': task.node.uuid})
return boot_iso_url
else:
swift_api = swift.SwiftAPI()
object_headers = None
if task.node.driver == 'redfish':
object_headers = {'X-Delete-After': str(timeout)}
swift_api.create_object(container, iso_object_name,
boot_iso_tmp_file,
object_headers=object_headers)
LOG.debug("Created ISO %(name)s in Swift for node %(node)s",
{'node': task.node.uuid, 'name': iso_object_name})
if task.node.driver == 'redfish':
boot_iso_url = swift_api.get_temp_url(
container, iso_object_name, timeout)
return boot_iso_url
else:
return 'swift:%s' % iso_object_name
def prepare_deploy_iso(task, params, mode, driver_info,
use_web_server=False, container=None):
"""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'.
:param driver_info: a dictionary containing driver_info values.
: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.
"""
kernel_href = driver_info.get('%s_kernel' % mode)
ramdisk_href = driver_info.get('%s_ramdisk' % mode)
bootloader_href = driver_info.get('bootloader')
timeout = None
if deploy_utils.get_boot_option(task.node) == "ramdisk":
i_info = task.node.instance_info
kernel_params = "root=/dev/ram0 text "
kernel_params += i_info.get("ramdisk_kernel_arguments", "")
elif task.node.driver == 'redfish':
kernel_params = CONF.redfish.kernel_append_params
timeout = CONF.redfish.swift_object_expiry_timeout
else:
kernel_params = CONF.pxe.pxe_append_params
if params:
kernel_params = ' '.join(
(kernel_params, ' '.join(
'%s=%s' % kv for kv in params.items())))
return prepare_iso_image(task, kernel_href, ramdisk_href,
bootloader_href=bootloader_href,
kernel_params=kernel_params, timeout=timeout,
use_web_server=use_web_server,
container=container)

View File

@ -15,9 +15,6 @@
"""Test class for boot methods used by iLO modules.""" """Test class for boot methods used by iLO modules."""
import io
import tempfile
from ironic_lib import utils as ironic_utils from ironic_lib import utils as ironic_utils
import mock import mock
from oslo_config import cfg from oslo_config import cfg
@ -39,6 +36,7 @@ from ironic.drivers.modules.ilo import management as ilo_management
from ironic.drivers.modules import ipxe from ironic.drivers.modules import ipxe
from ironic.drivers.modules import pxe from ironic.drivers.modules import pxe
from ironic.drivers.modules.storage import noop as noop_storage from ironic.drivers.modules.storage import noop as noop_storage
from ironic.drivers.modules import virtual_media_base
from ironic.drivers import utils as driver_utils from ironic.drivers import utils as driver_utils
from ironic.tests.unit.drivers.modules.ilo import test_common from ironic.tests.unit.drivers.modules.ilo import test_common
@ -50,13 +48,44 @@ class IloBootCommonMethodsTestCase(test_common.BaseIloTest):
boot_interface = 'ilo-virtual-media' boot_interface = 'ilo-virtual-media'
def test_parse_driver_info(self): def test_parse_driver_info_deploy_iso(self):
self.node.driver_info['ilo_deploy_iso'] = 'deploy-iso' self.node.driver_info['ilo_deploy_iso'] = 'deploy-iso'
expected_driver_info = {'ilo_deploy_iso': 'deploy-iso'} expected_driver_info = {'bootloader': None,
'ilo_deploy_iso': 'deploy-iso'}
actual_driver_info = ilo_boot.parse_driver_info(self.node) actual_driver_info = ilo_boot.parse_driver_info(self.node)
self.assertEqual(expected_driver_info, actual_driver_info) self.assertEqual(expected_driver_info, actual_driver_info)
def test_parse_driver_info_rescue_iso(self):
self.node.driver_info['ilo_rescue_iso'] = 'rescue-iso'
expected_driver_info = {'bootloader': None,
'ilo_rescue_iso': 'rescue-iso'}
actual_driver_info = ilo_boot.parse_driver_info(self.node, 'rescue')
self.assertEqual(expected_driver_info, actual_driver_info)
def test_parse_driver_info_deploy(self):
self.node.driver_info['deploy_kernel'] = 'kernel'
self.node.driver_info['deploy_ramdisk'] = 'ramdisk'
self.node.driver_info['bootloader'] = 'bootloader'
expected_driver_info = {'deploy_kernel': 'kernel',
'deploy_ramdisk': 'ramdisk',
'bootloader': 'bootloader'}
actual_driver_info = ilo_boot.parse_driver_info(self.node)
self.assertEqual(expected_driver_info, actual_driver_info)
def test_parse_driver_info_rescue(self):
self.node.driver_info['rescue_kernel'] = 'kernel'
self.node.driver_info['rescue_ramdisk'] = 'ramdisk'
self.node.driver_info['bootloader'] = 'bootloader'
expected_driver_info = {'rescue_kernel': 'kernel',
'rescue_ramdisk': 'ramdisk',
'bootloader': 'bootloader'}
actual_driver_info = ilo_boot.parse_driver_info(self.node, 'rescue')
self.assertEqual(expected_driver_info, actual_driver_info)
def test_parse_driver_info_exc(self): def test_parse_driver_info_exc(self):
self.assertRaises(exception.MissingParameterValue, self.assertRaises(exception.MissingParameterValue,
ilo_boot.parse_driver_info, self.node) ilo_boot.parse_driver_info, self.node)
@ -66,11 +95,6 @@ class IloBootPrivateMethodsTestCase(test_common.BaseIloTest):
boot_interface = 'ilo-virtual-media' boot_interface = 'ilo-virtual-media'
def test__get_boot_iso_object_name(self):
boot_iso_actual = ilo_boot._get_boot_iso_object_name(self.node)
boot_iso_expected = "boot-%s" % self.node.uuid
self.assertEqual(boot_iso_expected, boot_iso_actual)
@mock.patch.object(image_service.HttpImageService, 'validate_href', @mock.patch.object(image_service.HttpImageService, 'validate_href',
spec_set=True, autospec=True) spec_set=True, autospec=True)
def test__get_boot_iso_http_url(self, service_mock): def test__get_boot_iso_http_url(self, service_mock):
@ -128,71 +152,21 @@ class IloBootPrivateMethodsTestCase(test_common.BaseIloTest):
boot_iso_expected = u'glance://uui\u0111' boot_iso_expected = u'glance://uui\u0111'
self.assertEqual(boot_iso_expected, boot_iso_actual) self.assertEqual(boot_iso_expected, boot_iso_actual)
@mock.patch.object(boot_mode_utils, 'get_boot_mode_for_deploy', @mock.patch.object(virtual_media_base, 'prepare_iso_image', spec_set=True,
spec_set=True, autospec=True)
@mock.patch.object(ilo_boot.LOG, 'error', spec_set=True, autospec=True)
@mock.patch.object(images, 'get_image_properties', spec_set=True,
autospec=True)
@mock.patch.object(ilo_boot, '_parse_deploy_info', spec_set=True,
autospec=True)
def test__get_boot_iso_uefi_no_glance_image(self,
deploy_info_mock,
image_props_mock,
log_mock,
boot_mode_mock):
deploy_info_mock.return_value = {'image_source': 'image-uuid',
'ilo_deploy_iso': 'deploy_iso_uuid'}
image_props_mock.return_value = {'boot_iso': None,
'kernel_id': None,
'ramdisk_id': None}
properties = {'capabilities': 'boot_mode:uefi'}
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.node.properties = properties
boot_iso_result = ilo_boot._get_boot_iso(task, 'root-uuid')
deploy_info_mock.assert_called_once_with(task.node)
image_props_mock.assert_called_once_with(
task.context, 'image-uuid',
['boot_iso', 'kernel_id', 'ramdisk_id'])
self.assertTrue(log_mock.called)
self.assertFalse(boot_mode_mock.called)
self.assertIsNone(boot_iso_result)
@mock.patch.object(tempfile, 'NamedTemporaryFile', spec_set=True,
autospec=True)
@mock.patch.object(images, 'create_boot_iso', spec_set=True, autospec=True)
@mock.patch.object(swift, 'SwiftAPI', spec_set=True, autospec=True)
@mock.patch.object(ilo_boot, '_get_boot_iso_object_name', spec_set=True,
autospec=True)
@mock.patch.object(driver_utils, 'get_node_capability', spec_set=True,
autospec=True) autospec=True)
@mock.patch.object(images, 'get_image_properties', spec_set=True, @mock.patch.object(images, 'get_image_properties', spec_set=True,
autospec=True) autospec=True)
@mock.patch.object(ilo_boot, '_parse_deploy_info', spec_set=True, @mock.patch.object(ilo_boot, '_parse_deploy_info', spec_set=True,
autospec=True) autospec=True)
def test__get_boot_iso_create(self, deploy_info_mock, image_props_mock, def test__get_boot_iso_create(self, deploy_info_mock,
capability_mock, boot_object_name_mock, image_props_mock, prepare_iso_mock):
swift_api_mock,
create_boot_iso_mock, tempfile_mock):
CONF.ilo.swift_ilo_container = 'ilo-cont'
CONF.pxe.pxe_append_params = 'kernel-params'
swift_obj_mock = swift_api_mock.return_value
fileobj_mock = mock.MagicMock(spec=io.BytesIO)
fileobj_mock.name = 'tmpfile'
mock_file_handle = mock.MagicMock(spec=io.BytesIO)
mock_file_handle.__enter__.return_value = fileobj_mock
tempfile_mock.return_value = mock_file_handle
deploy_info_mock.return_value = {'image_source': 'image-uuid', deploy_info_mock.return_value = {'image_source': 'image-uuid',
'ilo_deploy_iso': 'deploy_iso_uuid'} 'ilo_deploy_iso': 'deploy_iso_uuid'}
image_props_mock.return_value = {'boot_iso': None, image_props_mock.return_value = {'boot_iso': None,
'kernel_id': 'kernel_uuid', 'kernel_id': 'kernel_uuid',
'ramdisk_id': 'ramdisk_uuid'} 'ramdisk_id': 'ramdisk_uuid'}
boot_object_name_mock.return_value = 'abcdef'
create_boot_iso_mock.return_value = '/path/to/boot-iso' prepare_iso_mock.return_value = 'swift:boot-iso'
capability_mock.return_value = 'uefi'
with task_manager.acquire(self.context, self.node.uuid, with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task: shared=False) as task:
boot_iso_actual = ilo_boot._get_boot_iso(task, 'root-uuid') boot_iso_actual = ilo_boot._get_boot_iso(task, 'root-uuid')
@ -200,166 +174,30 @@ class IloBootPrivateMethodsTestCase(test_common.BaseIloTest):
image_props_mock.assert_called_once_with( image_props_mock.assert_called_once_with(
task.context, 'image-uuid', task.context, 'image-uuid',
['boot_iso', 'kernel_id', 'ramdisk_id']) ['boot_iso', 'kernel_id', 'ramdisk_id'])
boot_object_name_mock.assert_called_once_with(task.node) prepare_iso_mock.assert_called_once_with(
create_boot_iso_mock.assert_called_once_with( task, 'kernel_uuid', 'ramdisk_uuid',
task.context, 'tmpfile', 'kernel_uuid', 'ramdisk_uuid',
deploy_iso_href='deploy_iso_uuid', deploy_iso_href='deploy_iso_uuid',
root_uuid='root-uuid', root_uuid='root-uuid', use_web_server=False,
kernel_params='kernel-params', container='ironic_ilo_container')
boot_mode='uefi') boot_iso_expected = 'swift:boot-iso'
swift_obj_mock.create_object.assert_called_once_with('ilo-cont',
'abcdef',
'tmpfile')
boot_iso_expected = 'swift:abcdef'
self.assertEqual(boot_iso_expected, boot_iso_actual) self.assertEqual(boot_iso_expected, boot_iso_actual)
@mock.patch.object(ilo_common, 'copy_image_to_web_server', spec_set=True, @mock.patch.object(virtual_media_base, 'get_iso_image_name',
autospec=True) spec_set=True, autospec=True)
@mock.patch.object(tempfile, 'NamedTemporaryFile', spec_set=True,
autospec=True)
@mock.patch.object(images, 'create_boot_iso', spec_set=True, autospec=True)
@mock.patch.object(ilo_boot, '_get_boot_iso_object_name', spec_set=True,
autospec=True)
@mock.patch.object(driver_utils, 'get_node_capability', spec_set=True,
autospec=True)
@mock.patch.object(images, 'get_image_properties', spec_set=True,
autospec=True)
@mock.patch.object(ilo_boot, '_parse_deploy_info', spec_set=True,
autospec=True)
def test__get_boot_iso_recreate_boot_iso_use_webserver(
self, deploy_info_mock, image_props_mock,
capability_mock, boot_object_name_mock,
create_boot_iso_mock, tempfile_mock,
copy_file_mock):
CONF.ilo.swift_ilo_container = 'ilo-cont'
CONF.ilo.use_web_server_for_images = True
CONF.deploy.http_url = "http://10.10.1.30/httpboot"
CONF.deploy.http_root = "/httpboot"
CONF.pxe.pxe_append_params = 'kernel-params'
fileobj_mock = mock.MagicMock(spec=io.BytesIO)
fileobj_mock.name = 'tmpfile'
mock_file_handle = mock.MagicMock(spec=io.BytesIO)
mock_file_handle.__enter__.return_value = fileobj_mock
tempfile_mock.return_value = mock_file_handle
ramdisk_href = "http://10.10.1.30/httpboot/ramdisk"
kernel_href = "http://10.10.1.30/httpboot/kernel"
deploy_info_mock.return_value = {'image_source': 'image-uuid',
'ilo_deploy_iso': 'deploy_iso_uuid'}
image_props_mock.return_value = {'boot_iso': None,
'kernel_id': kernel_href,
'ramdisk_id': ramdisk_href}
boot_object_name_mock.return_value = 'new_boot_iso'
create_boot_iso_mock.return_value = '/path/to/boot-iso'
capability_mock.return_value = 'uefi'
copy_file_mock.return_value = "http://10.10.1.30/httpboot/new_boot_iso"
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
driver_internal_info = task.node.driver_internal_info
driver_internal_info['boot_iso_created_in_web_server'] = True
instance_info = task.node.instance_info
old_boot_iso = 'http://10.10.1.30/httpboot/old_boot_iso'
instance_info['ilo_boot_iso'] = old_boot_iso
boot_iso_actual = ilo_boot._get_boot_iso(task, 'root-uuid')
deploy_info_mock.assert_called_once_with(task.node)
image_props_mock.assert_called_once_with(
task.context, 'image-uuid',
['boot_iso', 'kernel_id', 'ramdisk_id'])
boot_object_name_mock.assert_called_once_with(task.node)
create_boot_iso_mock.assert_called_once_with(
task.context, 'tmpfile', kernel_href, ramdisk_href,
deploy_iso_href='deploy_iso_uuid',
root_uuid='root-uuid',
kernel_params='kernel-params',
boot_mode='uefi')
boot_iso_expected = 'http://10.10.1.30/httpboot/new_boot_iso'
self.assertEqual(boot_iso_expected, boot_iso_actual)
copy_file_mock.assert_called_once_with(fileobj_mock.name,
'new_boot_iso')
@mock.patch.object(ilo_common, 'copy_image_to_web_server', spec_set=True,
autospec=True)
@mock.patch.object(tempfile, 'NamedTemporaryFile', spec_set=True,
autospec=True)
@mock.patch.object(images, 'create_boot_iso', spec_set=True, autospec=True)
@mock.patch.object(ilo_boot, '_get_boot_iso_object_name', spec_set=True,
autospec=True)
@mock.patch.object(driver_utils, 'get_node_capability', spec_set=True,
autospec=True)
@mock.patch.object(images, 'get_image_properties', spec_set=True,
autospec=True)
@mock.patch.object(ilo_boot, '_parse_deploy_info', spec_set=True,
autospec=True)
def test__get_boot_iso_create_use_webserver_true_ramdisk_webserver(
self, deploy_info_mock, image_props_mock,
capability_mock, boot_object_name_mock,
create_boot_iso_mock, tempfile_mock,
copy_file_mock):
CONF.ilo.swift_ilo_container = 'ilo-cont'
CONF.ilo.use_web_server_for_images = True
CONF.deploy.http_url = "http://10.10.1.30/httpboot"
CONF.deploy.http_root = "/httpboot"
CONF.pxe.pxe_append_params = 'kernel-params'
fileobj_mock = mock.MagicMock(spec=io.BytesIO)
fileobj_mock.name = 'tmpfile'
mock_file_handle = mock.MagicMock(spec=io.BytesIO)
mock_file_handle.__enter__.return_value = fileobj_mock
tempfile_mock.return_value = mock_file_handle
ramdisk_href = "http://10.10.1.30/httpboot/ramdisk"
kernel_href = "http://10.10.1.30/httpboot/kernel"
deploy_info_mock.return_value = {'image_source': 'image-uuid',
'ilo_deploy_iso': 'deploy_iso_uuid'}
image_props_mock.return_value = {'boot_iso': None,
'kernel_id': kernel_href,
'ramdisk_id': ramdisk_href}
boot_object_name_mock.return_value = 'abcdef'
create_boot_iso_mock.return_value = '/path/to/boot-iso'
capability_mock.return_value = 'uefi'
copy_file_mock.return_value = "http://10.10.1.30/httpboot/abcdef"
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
boot_iso_actual = ilo_boot._get_boot_iso(task, 'root-uuid')
deploy_info_mock.assert_called_once_with(task.node)
image_props_mock.assert_called_once_with(
task.context, 'image-uuid',
['boot_iso', 'kernel_id', 'ramdisk_id'])
boot_object_name_mock.assert_called_once_with(task.node)
create_boot_iso_mock.assert_called_once_with(
task.context, 'tmpfile', kernel_href, ramdisk_href,
deploy_iso_href='deploy_iso_uuid',
root_uuid='root-uuid',
kernel_params='kernel-params',
boot_mode='uefi')
boot_iso_expected = 'http://10.10.1.30/httpboot/abcdef'
self.assertEqual(boot_iso_expected, boot_iso_actual)
copy_file_mock.assert_called_once_with(fileobj_mock.name,
'abcdef')
@mock.patch.object(ilo_boot, '_get_boot_iso_object_name', spec_set=True,
autospec=True)
@mock.patch.object(swift, 'SwiftAPI', spec_set=True, autospec=True) @mock.patch.object(swift, 'SwiftAPI', spec_set=True, autospec=True)
def test__clean_up_boot_iso_for_instance(self, swift_mock, def test__clean_up_boot_iso_for_instance(self, swift_mock,
boot_object_name_mock): boot_object_name_mock):
swift_obj_mock = swift_mock.return_value swift_obj_mock = swift_mock.return_value
CONF.ilo.swift_ilo_container = 'ilo-cont' CONF.ilo.swift_ilo_container = 'ilo-cont'
boot_object_name_mock.return_value = 'boot-object' boot_object_name_mock.return_value = 'boot-object'
i_info = self.node.instance_info
i_info['ilo_boot_iso'] = 'swift:bootiso'
self.node.instance_info = i_info
self.node.save()
ilo_boot._clean_up_boot_iso_for_instance(self.node) ilo_boot._clean_up_boot_iso_for_instance(self.node)
swift_obj_mock.delete_object.assert_called_once_with('ilo-cont', swift_obj_mock.delete_object.assert_called_once_with('ilo-cont',
'boot-object') 'boot-object')
@mock.patch.object(ilo_boot.LOG, 'exception', spec_set=True, @mock.patch.object(ilo_boot.LOG, 'exception', spec_set=True,
autospec=True) autospec=True)
@mock.patch.object(ilo_boot, '_get_boot_iso_object_name', spec_set=True, @mock.patch.object(virtual_media_base, 'get_iso_image_name',
autospec=True) spec_set=True, autospec=True)
@mock.patch.object(swift, 'SwiftAPI', spec_set=True, autospec=True) @mock.patch.object(swift, 'SwiftAPI', spec_set=True, autospec=True)
def test__clean_up_boot_iso_for_instance_exc(self, swift_mock, def test__clean_up_boot_iso_for_instance_exc(self, swift_mock,
boot_object_name_mock, boot_object_name_mock,
@ -369,10 +207,6 @@ class IloBootPrivateMethodsTestCase(test_common.BaseIloTest):
swift_obj_mock.delete_object.side_effect = exc swift_obj_mock.delete_object.side_effect = exc
CONF.ilo.swift_ilo_container = 'ilo-cont' CONF.ilo.swift_ilo_container = 'ilo-cont'
boot_object_name_mock.return_value = 'boot-object' boot_object_name_mock.return_value = 'boot-object'
i_info = self.node.instance_info
i_info['ilo_boot_iso'] = 'swift:bootiso'
self.node.instance_info = i_info
self.node.save()
ilo_boot._clean_up_boot_iso_for_instance(self.node) ilo_boot._clean_up_boot_iso_for_instance(self.node)
swift_obj_mock.delete_object.assert_called_once_with('ilo-cont', swift_obj_mock.delete_object.assert_called_once_with('ilo-cont',
'boot-object') 'boot-object')
@ -380,25 +214,17 @@ class IloBootPrivateMethodsTestCase(test_common.BaseIloTest):
@mock.patch.object(ironic_utils, 'unlink_without_raise', spec_set=True, @mock.patch.object(ironic_utils, 'unlink_without_raise', spec_set=True,
autospec=True) autospec=True)
def test__clean_up_boot_iso_for_instance_on_webserver(self, unlink_mock): @mock.patch.object(virtual_media_base, 'get_iso_image_name',
spec_set=True, autospec=True)
def test__clean_up_boot_iso_for_instance_on_webserver(
self, boot_object_name_mock, unlink_mock):
boot_object_name_mock.return_value = 'boot-object'
CONF.ilo.use_web_server_for_images = True CONF.ilo.use_web_server_for_images = True
CONF.deploy.http_root = "/webserver" CONF.deploy.http_root = "/webserver"
i_info = self.node.instance_info
i_info['ilo_boot_iso'] = 'http://x.y.z.a/webserver/boot-object'
self.node.instance_info = i_info
self.node.save()
boot_iso_path = "/webserver/boot-object" boot_iso_path = "/webserver/boot-object"
ilo_boot._clean_up_boot_iso_for_instance(self.node) ilo_boot._clean_up_boot_iso_for_instance(self.node)
unlink_mock.assert_called_once_with(boot_iso_path) unlink_mock.assert_called_once_with(boot_iso_path)
@mock.patch.object(ilo_boot, '_get_boot_iso_object_name', spec_set=True,
autospec=True)
def test__clean_up_boot_iso_for_instance_no_boot_iso(
self, boot_object_name_mock):
ilo_boot._clean_up_boot_iso_for_instance(self.node)
self.assertFalse(boot_object_name_mock.called)
@mock.patch.object(ilo_boot, 'parse_driver_info', spec_set=True, @mock.patch.object(ilo_boot, 'parse_driver_info', spec_set=True,
autospec=True) autospec=True)
@mock.patch.object(deploy_utils, 'get_image_instance_info', @mock.patch.object(deploy_utils, 'get_image_instance_info',
@ -412,68 +238,15 @@ class IloBootPrivateMethodsTestCase(test_common.BaseIloTest):
@mock.patch.object(ilo_common, 'parse_driver_info', spec_set=True, @mock.patch.object(ilo_common, 'parse_driver_info', spec_set=True,
autospec=True) autospec=True)
def test__validate_driver_info_MissingParam(self, mock_parse_driver_info): @mock.patch.object(ilo_boot, 'parse_driver_info', spec_set=True,
autospec=True)
def test__validate_driver_info(self, mock_driver_info,
mock_parse_driver_info):
with task_manager.acquire(self.context, self.node.uuid, with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task: shared=False) as task:
self.assertRaisesRegex(exception.MissingParameterValue,
"Missing 'ilo_deploy_iso'",
ilo_boot._validate_driver_info, task)
mock_parse_driver_info.assert_called_once_with(task.node)
@mock.patch.object(service_utils, 'is_glance_image', spec_set=True,
autospec=True)
@mock.patch.object(ilo_common, 'parse_driver_info', spec_set=True,
autospec=True)
def test__validate_driver_info_valid_uuid(self, mock_parse_driver_info,
mock_is_glance_image):
mock_is_glance_image.return_value = True
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
deploy_iso = '8a81759a-f29b-454b-8ab3-161c6ca1882c'
task.node.driver_info['ilo_deploy_iso'] = deploy_iso
ilo_boot._validate_driver_info(task) ilo_boot._validate_driver_info(task)
mock_parse_driver_info.assert_called_once_with(task.node) mock_parse_driver_info.assert_called_once_with(task.node)
mock_is_glance_image.assert_called_once_with(deploy_iso) mock_driver_info.assert_called_once_with(task.node)
@mock.patch.object(image_service.HttpImageService, 'validate_href',
spec_set=True, autospec=True)
@mock.patch.object(service_utils, 'is_glance_image', spec_set=True,
autospec=True)
@mock.patch.object(ilo_common, 'parse_driver_info', spec_set=True,
autospec=True)
def test__validate_driver_info_InvalidParam(self, mock_parse_driver_info,
mock_is_glance_image,
mock_validate_href):
deploy_iso = 'http://abc.org/image/qcow2'
mock_validate_href.side_effect = exception.ImageRefValidationFailed(
image_href='http://abc.org/image/qcow2', reason='fail')
mock_is_glance_image.return_value = False
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.node.driver_info['ilo_deploy_iso'] = deploy_iso
self.assertRaisesRegex(exception.InvalidParameterValue,
"Virtual media boot accepts",
ilo_boot._validate_driver_info, task)
mock_parse_driver_info.assert_called_once_with(task.node)
mock_validate_href.assert_called_once_with(mock.ANY, deploy_iso)
@mock.patch.object(image_service.HttpImageService, 'validate_href',
spec_set=True, autospec=True)
@mock.patch.object(service_utils, 'is_glance_image', spec_set=True,
autospec=True)
@mock.patch.object(ilo_common, 'parse_driver_info', spec_set=True,
autospec=True)
def test__validate_driver_info_valid_url(self, mock_parse_driver_info,
mock_is_glance_image,
mock_validate_href):
deploy_iso = 'http://abc.org/image/deploy.iso'
mock_is_glance_image.return_value = False
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.node.driver_info['ilo_deploy_iso'] = deploy_iso
ilo_boot._validate_driver_info(task)
mock_parse_driver_info.assert_called_once_with(task.node)
mock_validate_href.assert_called_once_with(mock.ANY, deploy_iso)
@mock.patch.object(deploy_utils, 'validate_image_properties', @mock.patch.object(deploy_utils, 'validate_image_properties',
spec_set=True, autospec=True) spec_set=True, autospec=True)
@ -1044,10 +817,13 @@ class IloVirtualMediaBootTestCase(test_common.BaseIloTest):
@mock.patch.object(ilo_common, 'cleanup_vmedia_boot', spec_set=True, @mock.patch.object(ilo_common, 'cleanup_vmedia_boot', spec_set=True,
autospec=True) autospec=True)
def test_clean_up_ramdisk(self, cleanup_vmedia_mock): @mock.patch.object(ilo_boot, '_clean_up_boot_iso_for_instance',
spec_set=True, autospec=True)
def test_clean_up_ramdisk(self, cleanup_iso_mock, cleanup_vmedia_mock):
with task_manager.acquire(self.context, self.node.uuid, with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task: shared=False) as task:
task.driver.boot.clean_up_ramdisk(task) task.driver.boot.clean_up_ramdisk(task)
cleanup_iso_mock.assert_called_once_with(task.node)
cleanup_vmedia_mock.assert_called_once_with(task) cleanup_vmedia_mock.assert_called_once_with(task)
@mock.patch.object(deploy_utils, 'is_iscsi_boot', @mock.patch.object(deploy_utils, 'is_iscsi_boot',
@ -1228,7 +1004,7 @@ class IloVirtualMediaBootTestCase(test_common.BaseIloTest):
def test_validate_rescue_no_rescue_ramdisk(self): def test_validate_rescue_no_rescue_ramdisk(self):
with task_manager.acquire(self.context, self.node.uuid) as task: with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaisesRegex(exception.MissingParameterValue, self.assertRaisesRegex(exception.MissingParameterValue,
'Missing.*ilo_rescue_iso', 'Either.*ilo_rescue_iso*',
task.driver.boot.validate_rescue, task) task.driver.boot.validate_rescue, task)

View File

@ -19,7 +19,6 @@ import builtins
import hashlib import hashlib
import io import io
import os import os
import shutil
import tempfile import tempfile
from ironic_lib import utils as ironic_utils from ironic_lib import utils as ironic_utils
@ -341,7 +340,7 @@ class IloCommonMethodsTestCase(BaseIloTest):
'ilo_cont', object_name, timeout) 'ilo_cont', object_name, timeout)
self.assertEqual('temp-url', temp_url) self.assertEqual('temp-url', temp_url)
@mock.patch.object(ilo_common, 'copy_image_to_web_server', @mock.patch.object(deploy_utils, 'copy_image_to_web_server',
spec_set=True, autospec=True) spec_set=True, autospec=True)
@mock.patch.object(images, 'create_vfat_image', spec_set=True, @mock.patch.object(images, 'create_vfat_image', spec_set=True,
autospec=True) autospec=True)
@ -808,42 +807,6 @@ class IloCommonMethodsTestCase(BaseIloTest):
task, False) task, False)
ilo_mock_object.set_secure_boot_mode.assert_called_once_with(False) ilo_mock_object.set_secure_boot_mode.assert_called_once_with(False)
@mock.patch.object(os, 'chmod', spec_set=True,
autospec=True)
@mock.patch.object(shutil, 'copyfile', spec_set=True,
autospec=True)
def test_copy_image_to_web_server(self, copy_mock,
chmod_mock):
CONF.deploy.http_url = "http://x.y.z.a/webserver/"
CONF.deploy.http_root = "/webserver"
expected_url = "http://x.y.z.a/webserver/image-UUID"
source = 'tmp_image_file'
destination = "image-UUID"
image_path = "/webserver/image-UUID"
actual_url = ilo_common.copy_image_to_web_server(source, destination)
self.assertEqual(expected_url, actual_url)
copy_mock.assert_called_once_with(source, image_path)
chmod_mock.assert_called_once_with(image_path, 0o644)
@mock.patch.object(os, 'chmod', spec_set=True,
autospec=True)
@mock.patch.object(shutil, 'copyfile', spec_set=True,
autospec=True)
def test_copy_image_to_web_server_fails(self, copy_mock,
chmod_mock):
CONF.deploy.http_url = "http://x.y.z.a/webserver/"
CONF.deploy.http_root = "/webserver"
source = 'tmp_image_file'
destination = "image-UUID"
image_path = "/webserver/image-UUID"
exc = exception.ImageUploadFailed('reason')
copy_mock.side_effect = exc
self.assertRaises(exception.ImageUploadFailed,
ilo_common.copy_image_to_web_server,
source, destination)
copy_mock.assert_called_once_with(source, image_path)
self.assertFalse(chmod_mock.called)
@mock.patch.object(ilo_common, 'ironic_utils', autospec=True) @mock.patch.object(ilo_common, 'ironic_utils', autospec=True)
def test_remove_image_from_web_server(self, utils_mock): def test_remove_image_from_web_server(self, utils_mock):
# | GIVEN | # | GIVEN |

View File

@ -22,6 +22,7 @@ import mock
from oslo_utils import importutils from oslo_utils import importutils
from ironic.common import exception from ironic.common import exception
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.ilo import common as ilo_common from ironic.drivers.modules.ilo import common as ilo_common
from ironic.drivers.modules.ilo import firmware_processor as ilo_fw_processor from ironic.drivers.modules.ilo import firmware_processor as ilo_fw_processor
from ironic.tests import base from ironic.tests import base
@ -481,10 +482,12 @@ class FirmwareProcessorTestCase(base.TestCase):
utils_mock.process_firmware_image.assert_called_once_with( utils_mock.process_firmware_image.assert_called_once_with(
any_target_file, ilo_object_mock) any_target_file, ilo_object_mock)
@mock.patch.object(deploy_utils, 'copy_image_to_web_server',
autospec=True)
@mock.patch.object(ilo_fw_processor, 'ilo_common', autospec=True) @mock.patch.object(ilo_fw_processor, 'ilo_common', autospec=True)
@mock.patch.object(ilo_fw_processor, 'proliantutils_utils', autospec=True) @mock.patch.object(ilo_fw_processor, 'proliantutils_utils', autospec=True)
def test__extract_fw_from_file_doesnt_upload_firmware( def test__extract_fw_from_file_doesnt_upload_firmware(
self, utils_mock, ilo_common_mock): self, utils_mock, ilo_common_mock, copy_mock):
# | GIVEN | # | GIVEN |
node_mock = mock.MagicMock(uuid='fake_node_uuid') node_mock = mock.MagicMock(uuid='fake_node_uuid')
any_target_file = 'any_target_file' any_target_file = 'any_target_file'
@ -493,7 +496,7 @@ class FirmwareProcessorTestCase(base.TestCase):
# | WHEN | # | WHEN |
ilo_fw_processor._extract_fw_from_file(node_mock, any_target_file) ilo_fw_processor._extract_fw_from_file(node_mock, any_target_file)
# | THEN | # | THEN |
ilo_common_mock.copy_image_to_web_server.assert_not_called() copy_mock.assert_not_called()
@mock.patch.object(ilo_fw_processor, 'ilo_common', autospec=True) @mock.patch.object(ilo_fw_processor, 'ilo_common', autospec=True)
@mock.patch.object(ilo_fw_processor, 'proliantutils_utils', autospec=True) @mock.patch.object(ilo_fw_processor, 'proliantutils_utils', autospec=True)
@ -513,10 +516,12 @@ class FirmwareProcessorTestCase(base.TestCase):
# | THEN | # | THEN |
_remove_mock.assert_called_once_with(location_obj) _remove_mock.assert_called_once_with(location_obj)
@mock.patch.object(deploy_utils, 'copy_image_to_web_server',
autospec=True)
@mock.patch.object(ilo_fw_processor, 'ilo_common', autospec=True) @mock.patch.object(ilo_fw_processor, 'ilo_common', autospec=True)
@mock.patch.object(ilo_fw_processor, 'proliantutils_utils', autospec=True) @mock.patch.object(ilo_fw_processor, 'proliantutils_utils', autospec=True)
def test__extract_fw_from_file_uploads_firmware_to_webserver( def test__extract_fw_from_file_uploads_firmware_to_webserver(
self, utils_mock, ilo_common_mock): self, utils_mock, ilo_common_mock, copy_mock):
# | GIVEN | # | GIVEN |
node_mock = mock.MagicMock(uuid='fake_node_uuid') node_mock = mock.MagicMock(uuid='fake_node_uuid')
any_target_file = 'any_target_file' any_target_file = 'any_target_file'
@ -526,15 +531,17 @@ class FirmwareProcessorTestCase(base.TestCase):
# | WHEN | # | WHEN |
ilo_fw_processor._extract_fw_from_file(node_mock, any_target_file) ilo_fw_processor._extract_fw_from_file(node_mock, any_target_file)
# | THEN | # | THEN |
ilo_common_mock.copy_image_to_web_server.assert_called_once_with( copy_mock.assert_called_once_with(
'some_location/some_fw_file', 'some_fw_file') 'some_location/some_fw_file', 'some_fw_file')
@mock.patch.object(deploy_utils, 'copy_image_to_web_server',
autospec=True)
@mock.patch.object(ilo_fw_processor, 'ilo_common', autospec=True) @mock.patch.object(ilo_fw_processor, 'ilo_common', autospec=True)
@mock.patch.object(ilo_fw_processor, 'proliantutils_utils', autospec=True) @mock.patch.object(ilo_fw_processor, 'proliantutils_utils', autospec=True)
@mock.patch.object(ilo_fw_processor, '_remove_webserver_based_me', @mock.patch.object(ilo_fw_processor, '_remove_webserver_based_me',
autospec=True) autospec=True)
def test__extract_fw_from_file_sets_loc_obj_remove_to_webserver( def test__extract_fw_from_file_sets_loc_obj_remove_to_webserver(
self, _remove_mock, utils_mock, ilo_common_mock): self, _remove_mock, utils_mock, ilo_common_mock, copy_mock):
# | GIVEN | # | GIVEN |
node_mock = mock.MagicMock(uuid='fake_node_uuid') node_mock = mock.MagicMock(uuid='fake_node_uuid')
any_target_file = 'any_target_file' any_target_file = 'any_target_file'

View File

@ -27,6 +27,7 @@ from ironic.drivers.modules import boot_mode_utils
from ironic.drivers.modules import deploy_utils from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.redfish import boot as redfish_boot from ironic.drivers.modules.redfish import boot as redfish_boot
from ironic.drivers.modules.redfish import utils as redfish_utils from ironic.drivers.modules.redfish import utils as redfish_utils
from ironic.drivers.modules import virtual_media_base
from ironic.tests.unit.db import base as db_base from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.db import utils as db_utils from ironic.tests.unit.db import utils as db_utils
from ironic.tests.unit.objects import utils as obj_utils from ironic.tests.unit.objects import utils as obj_utils
@ -339,113 +340,10 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
mock_unpublish.assert_called_once_with(object_name) mock_unpublish.assert_called_once_with(object_name)
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, @mock.patch.object(virtual_media_base, 'prepare_iso_image', autospec=True)
'_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):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
task.node.instance_info.update(deploy_boot_mode='uefi')
expected_url = 'https://a.b/c.f?e=f'
mock__publish_image.return_value = expected_url
url = task.driver.boot._prepare_iso_image(
task, 'http://kernel/img', 'http://ramdisk/img',
'http://bootloader/img', root_uuid=task.node.uuid)
object_name = 'boot-%s' % task.node.uuid
mock__publish_image.assert_called_once_with(
mock.ANY, object_name)
mock_create_boot_iso.assert_called_once_with(
mock.ANY, mock.ANY, 'http://kernel/img', 'http://ramdisk/img',
boot_mode='uefi', esp_image_href='http://bootloader/img',
configdrive_href=mock.ANY,
kernel_params='nofb nomodeset vga=normal',
root_uuid='1be26c0b-03f2-4d2e-ae87-c02d7f33c123')
self.assertEqual(expected_url, url)
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
'_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):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
expected_url = 'https://a.b/c.f?e=f'
mock__publish_image.return_value = expected_url
url = task.driver.boot._prepare_iso_image(
task, 'http://kernel/img', 'http://ramdisk/img',
bootloader_href=None, root_uuid=task.node.uuid)
object_name = 'boot-%s' % task.node.uuid
mock__publish_image.assert_called_once_with(
mock.ANY, object_name)
mock_create_boot_iso.assert_called_once_with(
mock.ANY, mock.ANY, 'http://kernel/img', 'http://ramdisk/img',
boot_mode=None, esp_image_href=None,
configdrive_href=mock.ANY,
kernel_params='nofb nomodeset vga=normal',
root_uuid='1be26c0b-03f2-4d2e-ae87-c02d7f33c123')
self.assertEqual(expected_url, url)
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
'_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):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
kernel_params = 'network-config=base64-cloudinit-blob'
task.node.instance_info.update(kernel_append_params=kernel_params)
task.driver.boot._prepare_iso_image(
task, 'http://kernel/img', 'http://ramdisk/img',
bootloader_href=None, root_uuid=task.node.uuid)
mock_create_boot_iso.assert_called_once_with(
mock.ANY, mock.ANY, 'http://kernel/img', 'http://ramdisk/img',
boot_mode=None, esp_image_href=None,
configdrive_href=mock.ANY,
kernel_params=kernel_params,
root_uuid='1be26c0b-03f2-4d2e-ae87-c02d7f33c123')
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
'_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:
task.node.driver_info.update(
{'deploy_kernel': 'kernel',
'deploy_ramdisk': 'ramdisk',
'bootloader': 'bootloader'}
)
task.node.instance_info.update(deploy_boot_mode='uefi')
task.driver.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(images, 'create_boot_iso', autospec=True) @mock.patch.object(images, 'create_boot_iso', autospec=True)
def test__prepare_boot_iso(self, mock_create_boot_iso, def test__prepare_boot_iso(self, mock_create_boot_iso,
mock__prepare_iso_image): mock_prepare_iso_image):
with task_manager.acquire(self.context, self.node.uuid, with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task: shared=True) as task:
task.node.driver_info.update( task.node.driver_info.update(
@ -462,9 +360,11 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
task.driver.boot._prepare_boot_iso( task.driver.boot._prepare_boot_iso(
task, root_uuid=task.node.uuid) task, root_uuid=task.node.uuid)
mock__prepare_iso_image.assert_called_once_with( mock_prepare_iso_image.assert_called_once_with(
mock.ANY, 'http://kernel/img', 'http://ramdisk/img', mock.ANY, 'http://kernel/img', 'http://ramdisk/img',
'bootloader', root_uuid=task.node.uuid) bootloader_href='bootloader', root_uuid=task.node.uuid,
timeout=900, use_web_server=False,
container='ironic_redfish_container')
@mock.patch.object(redfish_utils, 'parse_driver_info', autospec=True) @mock.patch.object(redfish_utils, 'parse_driver_info', autospec=True)
@mock.patch.object(deploy_utils, 'validate_image_properties', @mock.patch.object(deploy_utils, 'validate_image_properties',
@ -557,8 +457,8 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
@mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device', @mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device',
autospec=True) autospec=True)
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, @mock.patch.object(virtual_media_base, 'prepare_deploy_iso',
'_prepare_deploy_iso', autospec=True) autospec=True)
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
'_eject_vmedia', autospec=True) '_eject_vmedia', autospec=True)
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
@ -571,14 +471,14 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
def test_prepare_ramdisk_with_params( def test_prepare_ramdisk_with_params(
self, mock_boot_mode_utils, mock_node_power_action, self, mock_boot_mode_utils, mock_node_power_action,
mock__parse_driver_info, mock__insert_vmedia, mock__eject_vmedia, mock__parse_driver_info, mock__insert_vmedia, mock__eject_vmedia,
mock__prepare_deploy_iso, mock_node_set_boot_device): mock_prepare_deploy_iso, mock_node_set_boot_device):
with task_manager.acquire(self.context, self.node.uuid, with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task: shared=False) as task:
task.node.provision_state = states.DEPLOYING task.node.provision_state = states.DEPLOYING
mock__parse_driver_info.return_value = {} mock__parse_driver_info.return_value = {}
mock__prepare_deploy_iso.return_value = 'image-url' mock_prepare_deploy_iso.return_value = 'image-url'
task.driver.boot.prepare_ramdisk(task, {}) task.driver.boot.prepare_ramdisk(task, {})
@ -597,8 +497,9 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
'ipa-debug': '1', 'ipa-debug': '1',
} }
mock__prepare_deploy_iso.assert_called_once_with( mock_prepare_deploy_iso.assert_called_once_with(
task, expected_params, 'deploy') task, expected_params, 'deploy', {},
use_web_server=False, container='ironic_redfish_container')
mock_node_set_boot_device.assert_called_once_with( mock_node_set_boot_device.assert_called_once_with(
task, boot_devices.CDROM, False) task, boot_devices.CDROM, False)
@ -607,8 +508,8 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
@mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device', @mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device',
autospec=True) autospec=True)
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, @mock.patch.object(virtual_media_base, 'prepare_deploy_iso',
'_prepare_deploy_iso', autospec=True) autospec=True)
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
'_eject_vmedia', autospec=True) '_eject_vmedia', autospec=True)
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
@ -621,14 +522,14 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
def test_prepare_ramdisk_no_debug( def test_prepare_ramdisk_no_debug(
self, mock_boot_mode_utils, mock_node_power_action, self, mock_boot_mode_utils, mock_node_power_action,
mock__parse_driver_info, mock__insert_vmedia, mock__eject_vmedia, mock__parse_driver_info, mock__insert_vmedia, mock__eject_vmedia,
mock__prepare_deploy_iso, mock_node_set_boot_device): mock_prepare_deploy_iso, mock_node_set_boot_device):
self.config(debug=False) self.config(debug=False)
with task_manager.acquire(self.context, self.node.uuid, with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task: shared=True) as task:
task.node.provision_state = states.DEPLOYING task.node.provision_state = states.DEPLOYING
mock__parse_driver_info.return_value = {} mock__parse_driver_info.return_value = {}
mock__prepare_deploy_iso.return_value = 'image-url' mock_prepare_deploy_iso.return_value = 'image-url'
task.driver.boot.prepare_ramdisk(task, {}) task.driver.boot.prepare_ramdisk(task, {})
@ -646,8 +547,9 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
'ipa-agent-token': mock.ANY, 'ipa-agent-token': mock.ANY,
} }
mock__prepare_deploy_iso.assert_called_once_with( mock_prepare_deploy_iso.assert_called_once_with(
task, expected_params, 'deploy') task, expected_params, 'deploy', {},
use_web_server=False, container='ironic_redfish_container')
mock_node_set_boot_device.assert_called_once_with( mock_node_set_boot_device.assert_called_once_with(
task, boot_devices.CDROM, False) task, boot_devices.CDROM, False)
@ -658,8 +560,8 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
autospec=True) autospec=True)
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
'_prepare_floppy_image', autospec=True) '_prepare_floppy_image', autospec=True)
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, @mock.patch.object(virtual_media_base, 'prepare_deploy_iso',
'_prepare_deploy_iso', autospec=True) autospec=True)
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
'_has_vmedia_device', autospec=True) '_has_vmedia_device', autospec=True)
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
@ -674,7 +576,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
def test_prepare_ramdisk_with_floppy( def test_prepare_ramdisk_with_floppy(
self, mock_boot_mode_utils, mock_node_power_action, self, mock_boot_mode_utils, mock_node_power_action,
mock__parse_driver_info, mock__insert_vmedia, mock__eject_vmedia, mock__parse_driver_info, mock__insert_vmedia, mock__eject_vmedia,
mock__has_vmedia_device, mock__prepare_deploy_iso, mock__has_vmedia_device, mock_prepare_deploy_iso,
mock__prepare_floppy_image, mock_node_set_boot_device): mock__prepare_floppy_image, mock_node_set_boot_device):
with task_manager.acquire(self.context, self.node.uuid, with task_manager.acquire(self.context, self.node.uuid,
@ -687,7 +589,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
mock__has_vmedia_device.return_value = True mock__has_vmedia_device.return_value = True
mock__prepare_floppy_image.return_value = 'floppy-image-url' mock__prepare_floppy_image.return_value = 'floppy-image-url'
mock__prepare_deploy_iso.return_value = 'cd-image-url' mock_prepare_deploy_iso.return_value = 'cd-image-url'
task.driver.boot.prepare_ramdisk(task, {}) task.driver.boot.prepare_ramdisk(task, {})
@ -720,8 +622,11 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
'ipa-agent-token': mock.ANY, 'ipa-agent-token': mock.ANY,
} }
mock__prepare_deploy_iso.assert_called_once_with( expected_d_info = {'config_via_floppy': True}
task, expected_params, 'deploy')
mock_prepare_deploy_iso.assert_called_once_with(
task, expected_params, 'deploy', expected_d_info,
use_web_server=False, container='ironic_redfish_container')
mock_node_set_boot_device.assert_called_once_with( mock_node_set_boot_device.assert_called_once_with(
task, boot_devices.CDROM, False) task, boot_devices.CDROM, False)

View File

@ -15,6 +15,7 @@
# under the License. # under the License.
import os import os
import shutil
import tempfile import tempfile
import fixtures import fixtures
@ -1241,6 +1242,38 @@ class AgentMethodsTestCase(db_base.DbTestCase):
self.assertTrue( self.assertTrue(
utils.direct_deploy_should_convert_raw_image(self.node)) utils.direct_deploy_should_convert_raw_image(self.node))
@mock.patch.object(shutil, 'copyfile', spec_set=True,
autospec=True)
def test_copy_image_to_web_server(self, copy_mock):
cfg.CONF.deploy.http_url = "http://x.y.z.a/webserver/"
cfg.CONF.deploy.http_root = "/webserver"
expected_url = "http://x.y.z.a/webserver/image-UUID"
source = 'tmp_image_file'
destination = "image-UUID"
image_path = "/webserver/image-UUID"
actual_url = utils.copy_image_to_web_server(source, destination)
self.assertEqual(expected_url, actual_url)
copy_mock.assert_called_once_with(source, image_path)
@mock.patch.object(os, 'chmod', spec_set=True,
autospec=True)
@mock.patch.object(shutil, 'copyfile', spec_set=True,
autospec=True)
def test_copy_image_to_web_server_fails(self, copy_mock,
chmod_mock):
cfg.CONF.deploy.http_url = "http://x.y.z.a/webserver/"
cfg.CONF.deploy.http_root = "/webserver"
source = 'tmp_image_file'
destination = "image-UUID"
image_path = "/webserver/image-UUID"
exc = exception.ImageUploadFailed('reason')
copy_mock.side_effect = exc
self.assertRaises(exception.ImageUploadFailed,
utils.copy_image_to_web_server,
source, destination)
copy_mock.assert_called_once_with(source, image_path)
self.assertFalse(chmod_mock.called)
class ValidateImagePropertiesTestCase(db_base.DbTestCase): class ValidateImagePropertiesTestCase(db_base.DbTestCase):

View File

@ -0,0 +1,232 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import tempfile
import mock
from oslo_config import cfg
import six
from ironic.common import images
from ironic.common import swift
from ironic.conductor import task_manager
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules import virtual_media_base
from ironic.drivers import utils as driver_utils
from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.db import utils as db_utils
from ironic.tests.unit.objects import utils as object_utils
if six.PY3:
import io
file = io.BytesIO
INFO_DICT = db_utils.get_test_redfish_info()
CONF = cfg.CONF
class VirtualMediaCommonMethodsTestCase(db_base.DbTestCase):
def setUp(self):
super(VirtualMediaCommonMethodsTestCase, self).setUp()
self.config(enabled_hardware_types=['ilo', 'fake-hardware'],
enabled_boot_interfaces=['ilo-pxe', 'ilo-virtual-media',
'fake'],
enabled_bios_interfaces=['ilo', 'no-bios'],
enabled_power_interfaces=['ilo', 'fake'],
enabled_management_interfaces=['ilo', 'fake'],
enabled_inspect_interfaces=['ilo', 'fake', 'no-inspect'],
enabled_console_interfaces=['ilo', 'fake', 'no-console'],
enabled_vendor_interfaces=['ilo', 'fake', 'no-vendor'])
self.node = object_utils.create_test_node(
self.context, boot_interface='ilo-virtual-media',
deploy_interface='direct')
def test_get_iso_image_name(self):
boot_iso_actual = virtual_media_base.get_iso_image_name(self.node)
boot_iso_expected = "boot-%s" % self.node.uuid
self.assertEqual(boot_iso_expected, boot_iso_actual)
@mock.patch.object(tempfile, 'NamedTemporaryFile', spec_set=True,
autospec=True)
@mock.patch.object(images, 'create_boot_iso', spec_set=True, autospec=True)
@mock.patch.object(swift, 'SwiftAPI', spec_set=True, autospec=True)
@mock.patch.object(virtual_media_base, 'get_iso_image_name',
spec_set=True, autospec=True)
@mock.patch.object(driver_utils, 'get_node_capability', spec_set=True,
autospec=True)
def test__prepare_iso_image_uefi(self, capability_mock,
iso_image_name_mock, swift_api_mock,
create_boot_iso_mock, tempfile_mock):
CONF.ilo.swift_ilo_container = 'ilo-cont'
CONF.ilo.use_web_server_for_images = False
swift_obj_mock = swift_api_mock.return_value
fileobj_mock = mock.MagicMock(spec=file)
fileobj_mock.name = 'tmpfile'
mock_file_handle = mock.MagicMock(spec=file)
mock_file_handle.__enter__.return_value = fileobj_mock
tempfile_mock.return_value = mock_file_handle
iso_image_name_mock.return_value = 'abcdef'
create_boot_iso_mock.return_value = '/path/to/boot-iso'
capability_mock.return_value = 'uefi'
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
boot_iso_actual = virtual_media_base.prepare_iso_image(
task, 'kernel_uuid', 'ramdisk_uuid',
deploy_iso_href='deploy_iso_uuid',
bootloader_href='bootloader_uuid',
root_uuid='root-uuid',
kernel_params='kernel-params',
timeout=None,
container=CONF.ilo.swift_ilo_container,
use_web_server=CONF.ilo.use_web_server_for_images)
iso_image_name_mock.assert_called_once_with(task.node)
create_boot_iso_mock.assert_called_once_with(
task.context, 'tmpfile', 'kernel_uuid', 'ramdisk_uuid',
deploy_iso_href='deploy_iso_uuid',
esp_image_href='bootloader_uuid',
root_uuid='root-uuid',
kernel_params='kernel-params',
boot_mode='uefi')
swift_obj_mock.create_object.assert_called_once_with(
'ilo-cont', 'abcdef', 'tmpfile', None)
boot_iso_expected = 'swift:abcdef'
self.assertEqual(boot_iso_expected, boot_iso_actual)
@mock.patch.object(tempfile, 'NamedTemporaryFile', spec_set=True,
autospec=True)
@mock.patch.object(images, 'create_boot_iso', spec_set=True, autospec=True)
@mock.patch.object(swift, 'SwiftAPI', spec_set=True, autospec=True)
@mock.patch.object(virtual_media_base, 'get_iso_image_name',
spec_set=True, autospec=True)
@mock.patch.object(driver_utils, 'get_node_capability', spec_set=True,
autospec=True)
def test__prepare_iso_image_bios(self, capability_mock,
iso_image_name_mock, swift_api_mock,
create_boot_iso_mock, tempfile_mock):
CONF.ilo.swift_ilo_container = 'ilo-cont'
CONF.ilo.use_web_server_for_images = False
swift_obj_mock = swift_api_mock.return_value
fileobj_mock = mock.MagicMock(spec=file)
fileobj_mock.name = 'tmpfile'
mock_file_handle = mock.MagicMock(spec=file)
mock_file_handle.__enter__.return_value = fileobj_mock
tempfile_mock.return_value = mock_file_handle
iso_image_name_mock.return_value = 'abcdef'
create_boot_iso_mock.return_value = '/path/to/boot-iso'
capability_mock.return_value = 'bios'
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
boot_iso_actual = virtual_media_base.prepare_iso_image(
task, 'kernel_uuid', 'ramdisk_uuid',
deploy_iso_href='deploy_iso_uuid',
root_uuid='root-uuid',
kernel_params='kernel-params',
timeout=None,
container=CONF.ilo.swift_ilo_container,
use_web_server=CONF.ilo.use_web_server_for_images)
iso_image_name_mock.assert_called_once_with(task.node)
create_boot_iso_mock.assert_called_once_with(
task.context, 'tmpfile', 'kernel_uuid', 'ramdisk_uuid',
deploy_iso_href='deploy_iso_uuid',
esp_image_href=None,
root_uuid='root-uuid',
kernel_params='kernel-params',
boot_mode='bios')
swift_obj_mock.create_object.assert_called_once_with(
'ilo-cont', 'abcdef', 'tmpfile', None)
boot_iso_expected = 'swift:abcdef'
self.assertEqual(boot_iso_expected, boot_iso_actual)
@mock.patch.object(deploy_utils, 'copy_image_to_web_server', spec_set=True,
autospec=True)
@mock.patch.object(tempfile, 'NamedTemporaryFile', spec_set=True,
autospec=True)
@mock.patch.object(images, 'create_boot_iso', spec_set=True, autospec=True)
@mock.patch.object(virtual_media_base, 'get_iso_image_name',
spec_set=True, autospec=True)
@mock.patch.object(driver_utils, 'get_node_capability', spec_set=True,
autospec=True)
def test__prepare_iso_image_use_webserver(self, capability_mock,
iso_image_name_mock,
create_boot_iso_mock,
tempfile_mock, copy_file_mock):
CONF.ilo.use_web_server_for_images = True
CONF.deploy.http_url = "http://10.10.1.30/httpboot"
CONF.deploy.http_root = "/httpboot"
CONF.pxe.pxe_append_params = 'kernel-params'
fileobj_mock = mock.MagicMock(spec=file)
fileobj_mock.name = 'tmpfile'
mock_file_handle = mock.MagicMock(spec=file)
mock_file_handle.__enter__.return_value = fileobj_mock
tempfile_mock.return_value = mock_file_handle
ramdisk_href = "http://10.10.1.30/httpboot/ramdisk"
kernel_href = "http://10.10.1.30/httpboot/kernel"
iso_image_name_mock.return_value = 'new_boot_iso'
create_boot_iso_mock.return_value = '/path/to/boot-iso'
capability_mock.return_value = 'uefi'
copy_file_mock.return_value = "http://10.10.1.30/httpboot/new_boot_iso"
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
driver_internal_info = task.node.driver_internal_info
driver_internal_info['boot_iso_created_in_web_server'] = True
boot_iso_actual = virtual_media_base.prepare_iso_image(
task, kernel_href, ramdisk_href,
deploy_iso_href='deploy_iso_uuid',
bootloader_href='bootloader_uuid',
root_uuid='root-uuid',
kernel_params='kernel-params',
use_web_server=CONF.ilo.use_web_server_for_images)
iso_image_name_mock.assert_called_once_with(task.node)
create_boot_iso_mock.assert_called_once_with(
task.context, 'tmpfile', kernel_href, ramdisk_href,
deploy_iso_href='deploy_iso_uuid',
esp_image_href='bootloader_uuid',
root_uuid='root-uuid',
kernel_params='kernel-params',
boot_mode='uefi')
boot_iso_expected = 'http://10.10.1.30/httpboot/new_boot_iso'
self.assertEqual(boot_iso_expected, boot_iso_actual)
copy_file_mock.assert_called_once_with(fileobj_mock.name,
'new_boot_iso')
@mock.patch.object(virtual_media_base, 'prepare_iso_image', spec_set=True,
autospec=True)
def test_prepare_deploy_iso(self, prepare_iso_mock):
driver_info = {'deploy_kernel': 'kernel', 'deploy_ramdisk': 'ramdisk',
'bootloader': 'bootloader'}
CONF.pxe.pxe_append_params = 'kernel-params'
timeout = None
container = 'container'
prepare_iso_mock.return_value = (
'swift:boot-b5451849-e088-4a4c-aa5f-4d97b3371dec')
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
deploy_iso_actual = virtual_media_base.prepare_deploy_iso(
task, {}, 'deploy', driver_info, use_web_server=False,
container=container)
prepare_iso_mock.assert_called_once_with(
task, 'kernel', 'ramdisk', bootloader_href='bootloader',
kernel_params=CONF.pxe.pxe_append_params, timeout=timeout,
use_web_server=False, container='container')
deploy_iso_expected = (
'swift:boot-b5451849-e088-4a4c-aa5f-4d97b3371dec')
self.assertEqual(deploy_iso_expected, deploy_iso_actual)

View File

@ -0,0 +1,8 @@
features:
- |
Add functionality to perform virtual media boot without
user-built deploy/rescue/boot ISO images for hardware type ilo.
Instead, ironic will build necessary images out of common
kernel/ramdisk pair (though user needs to provide ESP image).
User provided deploy/rescue/boot ISO images are
also supported.