Merge "Decouple the ISO creation logic from redfish"
This commit is contained in:
commit
aa106ff205
501
ironic/drivers/modules/image_utils.py
Normal file
501
ironic/drivers/modules/image_utils.py
Normal file
@ -0,0 +1,501 @@
|
||||
# Copyright 2019 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 functools
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from urllib import parse as urlparse
|
||||
|
||||
from ironic_lib import utils as ironic_utils
|
||||
from oslo_log import log
|
||||
from oslo_serialization import base64
|
||||
|
||||
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__)
|
||||
|
||||
|
||||
class ImageHandler(object):
|
||||
|
||||
_SWIFT_MAP = {
|
||||
"redfish": {
|
||||
"swift_enabled": CONF.redfish.use_swift,
|
||||
"container": CONF.redfish.swift_container,
|
||||
"timeout": CONF.redfish.swift_object_expiry_timeout,
|
||||
"image_subdir": "redfish",
|
||||
"file_permission": CONF.redfish.file_permission
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, driver):
|
||||
self._driver = driver
|
||||
self._container = self._SWIFT_MAP[driver].get("container")
|
||||
self._timeout = self._SWIFT_MAP[driver].get("timeout")
|
||||
self._image_subdir = self._SWIFT_MAP[driver].get("image_subdir")
|
||||
self._file_permission = self._SWIFT_MAP[driver].get("file_permission")
|
||||
|
||||
def _is_swift_enabled(self):
|
||||
try:
|
||||
return self._SWIFT_MAP[self._driver].get("swift_enabled")
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
def unpublish_image(self, 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 self._is_swift_enabled():
|
||||
container = self._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, self._image_subdir, object_name)
|
||||
|
||||
ironic_utils.unlink_without_raise(published_file)
|
||||
|
||||
def _append_filename_param(self, url, filename):
|
||||
"""Append 'filename=<file>' 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 publish_image(self, 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 self._is_swift_enabled():
|
||||
container = self._container
|
||||
timeout = self._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,
|
||||
self._image_subdir)
|
||||
|
||||
if not os.path.exists(public_dir):
|
||||
os.mkdir(public_dir, 0o755)
|
||||
|
||||
published_file = os.path.join(public_dir, object_name)
|
||||
|
||||
try:
|
||||
os.link(image_file, published_file)
|
||||
os.chmod(image_file, self._file_permission)
|
||||
|
||||
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)
|
||||
os.chmod(published_file, self._file_permission)
|
||||
|
||||
image_url = os.path.join(
|
||||
CONF.deploy.http_url, self._image_subdir, object_name)
|
||||
|
||||
image_url = self._append_filename_param(
|
||||
image_url, os.path.basename(image_file))
|
||||
|
||||
return image_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 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)
|
||||
img_handler = ImageHandler(task.node.driver)
|
||||
|
||||
img_handler.unpublish_image(iso_object_name)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
img_handler = ImageHandler(task.node.driver)
|
||||
|
||||
image_url = img_handler.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 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)
|
||||
|
||||
img_handler = ImageHandler(task.node.driver)
|
||||
img_handler.unpublish_image(floppy_object_name)
|
||||
|
||||
|
||||
def _prepare_iso_image(task, kernel_href, ramdisk_href,
|
||||
bootloader_href=None, configdrive=None,
|
||||
root_uuid=None, params=None, base_iso=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.
|
||||
|
||||
If `configdrive` is specified it will be eventually written onto
|
||||
the boot ISO image.
|
||||
|
||||
: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) and not base_iso:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Unable to find kernel, ramdisk for "
|
||||
"building ISO, or explicit ISO for %(node)s") %
|
||||
{'node': task.node.uuid})
|
||||
|
||||
i_info = task.node.instance_info
|
||||
|
||||
# NOTE(TheJulia): Until we support modifying a base iso, most of
|
||||
# this logic actually does nothing in the end. But it should!
|
||||
if deploy_utils.get_boot_option(task.node) == "ramdisk":
|
||||
if not base_iso:
|
||||
kernel_params = "root=/dev/ram0 text "
|
||||
kernel_params += i_info.get("ramdisk_kernel_arguments", "")
|
||||
else:
|
||||
kernel_params = None
|
||||
|
||||
else:
|
||||
kernel_params = i_info.get(
|
||||
'kernel_append_params', CONF.redfish.kernel_append_params)
|
||||
|
||||
if params and not base_iso:
|
||||
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
|
||||
|
||||
# FIXME(TheJulia): This is treated as conditional with
|
||||
# a base_iso as the intent, eventually, is to support
|
||||
# injection into the supplied image.
|
||||
|
||||
if configdrive and not base_iso:
|
||||
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.debug("Built configdrive out of configdrive blob "
|
||||
"for node %(node)s", {'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,
|
||||
base_iso=base_iso)
|
||||
|
||||
iso_object_name = _get_iso_image_name(task.node)
|
||||
|
||||
img_handler = ImageHandler(task.node.driver)
|
||||
image_url = img_handler.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, d_info):
|
||||
"""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.
|
||||
|
||||
If network interface supplies network configuration (`network_data`),
|
||||
a new `configdrive` will be created with `network_data.json` inside,
|
||||
and eventually written down onto the boot ISO.
|
||||
|
||||
: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 d_info: Deployment information of the node
|
||||
: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 = d_info.get('%s_kernel' % mode)
|
||||
ramdisk_href = d_info.get('%s_ramdisk' % mode)
|
||||
bootloader_href = d_info.get('bootloader')
|
||||
|
||||
# TODO(TheJulia): At some point we should support something like
|
||||
# boot_iso for the deploy interface, perhaps when we support config
|
||||
# injection.
|
||||
prepare_iso_image = functools.partial(
|
||||
_prepare_iso_image, task, kernel_href, ramdisk_href,
|
||||
bootloader_href=bootloader_href, params=params)
|
||||
|
||||
network_data = task.driver.network.get_node_network_data(task)
|
||||
if network_data:
|
||||
with tempfile.NamedTemporaryFile(dir=CONF.tempdir,
|
||||
suffix='.iso') as metadata_fileobj:
|
||||
|
||||
with open(metadata_fileobj.name, 'w') as f:
|
||||
json.dump(network_data, f, indent=2)
|
||||
|
||||
files_info = {
|
||||
metadata_fileobj.name: 'openstack/latest/meta'
|
||||
'data/network_data.json'
|
||||
}
|
||||
|
||||
with tempfile.NamedTemporaryFile(
|
||||
dir=CONF.tempdir, suffix='.img') as cfgdrv_fileobj:
|
||||
|
||||
images.create_vfat_image(cfgdrv_fileobj.name, files_info)
|
||||
|
||||
configdrive_href = urlparse.urlunparse(
|
||||
('file', '', cfgdrv_fileobj.name, '', '', ''))
|
||||
|
||||
LOG.debug("Built configdrive %(name)s out of network data "
|
||||
"for node %(node)s", {'name': configdrive_href,
|
||||
'node': task.node.uuid})
|
||||
|
||||
return prepare_iso_image(configdrive=configdrive_href)
|
||||
|
||||
return prepare_iso_image()
|
||||
|
||||
|
||||
def prepare_boot_iso(task, d_info, 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.
|
||||
:param d_info: Deployment information of the node
|
||||
:param root_uuid: Root UUID
|
||||
: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
|
||||
|
||||
kernel_href = node.instance_info.get('kernel')
|
||||
ramdisk_href = node.instance_info.get('ramdisk')
|
||||
base_iso = node.instance_info.get('boot_iso')
|
||||
|
||||
if (not kernel_href or not ramdisk_href) and not base_iso:
|
||||
|
||||
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, base_iso=base_iso)
|
@ -13,30 +13,20 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import functools
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from urllib import parse as urlparse
|
||||
|
||||
from ironic_lib import utils as ironic_utils
|
||||
from oslo_log import log
|
||||
from oslo_serialization import base64
|
||||
from oslo_utils import importutils
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import exception
|
||||
from ironic.common.glance_service import service_utils
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common import images
|
||||
from ironic.common import states
|
||||
from ironic.common import swift
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.conf import CONF
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules import boot_mode_utils
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
from ironic.drivers.modules import image_utils
|
||||
from ironic.drivers.modules.redfish import utils as redfish_utils
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
@ -151,52 +141,6 @@ def _parse_instance_info(node):
|
||||
return deploy_info
|
||||
|
||||
|
||||
def _append_filename_param(url, filename):
|
||||
"""Append 'filename=<file>' 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
|
||||
|
||||
@ -283,157 +227,6 @@ def _has_vmedia_device(task, boot_device):
|
||||
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, 0o755)
|
||||
|
||||
published_file = os.path.join(public_dir, object_name)
|
||||
|
||||
try:
|
||||
os.link(image_file, published_file)
|
||||
os.chmod(image_file, CONF.redfish.file_permission)
|
||||
|
||||
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)
|
||||
os.chmod(published_file, CONF.redfish.file_permission)
|
||||
|
||||
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.
|
||||
|
||||
@ -456,248 +249,6 @@ def _parse_deploy_info(node):
|
||||
return deploy_info
|
||||
|
||||
|
||||
def _prepare_iso_image(task, kernel_href, ramdisk_href,
|
||||
bootloader_href=None, configdrive=None,
|
||||
root_uuid=None, params=None, base_iso=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.
|
||||
|
||||
If `configdrive` is specified it will be eventually written onto
|
||||
the boot ISO image.
|
||||
|
||||
: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) and not base_iso:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Unable to find kernel, ramdisk for "
|
||||
"building ISO, or explicit ISO for %(node)s") %
|
||||
{'node': task.node.uuid})
|
||||
|
||||
i_info = task.node.instance_info
|
||||
|
||||
# NOTE(TheJulia): Until we support modifying a base iso, most of
|
||||
# this logic actually does nothing in the end. But it should!
|
||||
if deploy_utils.get_boot_option(task.node) == "ramdisk":
|
||||
if not base_iso:
|
||||
kernel_params = "root=/dev/ram0 text "
|
||||
kernel_params += i_info.get("ramdisk_kernel_arguments", "")
|
||||
else:
|
||||
kernel_params = None
|
||||
|
||||
else:
|
||||
kernel_params = i_info.get(
|
||||
'kernel_append_params', CONF.redfish.kernel_append_params)
|
||||
|
||||
if params and not base_iso:
|
||||
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
|
||||
|
||||
# FIXME(TheJulia): This is treated as conditional with
|
||||
# a base_iso as the intent, eventually, is to support
|
||||
# injection into the supplied image.
|
||||
|
||||
if configdrive and not base_iso:
|
||||
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.debug("Built configdrive out of configdrive blob "
|
||||
"for node %(node)s", {'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,
|
||||
base_iso=base_iso)
|
||||
|
||||
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.
|
||||
|
||||
If network interface supplies network configuration (`network_data`),
|
||||
a new `configdrive` will be created with `network_data.json` inside,
|
||||
and eventually written down onto the boot ISO.
|
||||
|
||||
: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')
|
||||
|
||||
# TODO(TheJulia): At some point we should support something like
|
||||
# boot_iso for the deploy interface, perhaps when we support config
|
||||
# injection.
|
||||
prepare_iso_image = functools.partial(
|
||||
_prepare_iso_image, task, kernel_href, ramdisk_href,
|
||||
bootloader_href=bootloader_href, params=params)
|
||||
|
||||
network_data = task.driver.network.get_node_network_data(task)
|
||||
if network_data:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
dir=CONF.tempdir, suffix='.iso') as metadata_fileobj:
|
||||
|
||||
with open(metadata_fileobj.name, 'w') as f:
|
||||
json.dump(network_data, f, indent=2)
|
||||
|
||||
files_info = {
|
||||
metadata_fileobj.name: 'openstack/latest/'
|
||||
'network_data.json'
|
||||
}
|
||||
|
||||
with tempfile.NamedTemporaryFile(
|
||||
dir=CONF.tempdir, suffix='.img') as cfgdrv_fileobj:
|
||||
|
||||
images.create_vfat_image(cfgdrv_fileobj.name, files_info)
|
||||
|
||||
configdrive_href = urlparse.urlunparse(
|
||||
('file', '', cfgdrv_fileobj.name, '', '', ''))
|
||||
|
||||
LOG.debug("Built configdrive %(name)s out of network data "
|
||||
"for node %(node)s", {'name': configdrive_href,
|
||||
'node': task.node.uuid})
|
||||
|
||||
return prepare_iso_image(configdrive=configdrive_href)
|
||||
|
||||
return prepare_iso_image()
|
||||
|
||||
|
||||
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')
|
||||
base_iso = node.instance_info.get('boot_iso')
|
||||
|
||||
if (not kernel_href or not ramdisk_href) and not base_iso:
|
||||
|
||||
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, base_iso=base_iso)
|
||||
|
||||
|
||||
class RedfishVirtualMediaBoot(base.BootInterface):
|
||||
"""Virtual media boot interface over Redfish.
|
||||
|
||||
@ -875,7 +426,7 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
||||
# we tell it to
|
||||
ramdisk_params['boot_method'] = 'vmedia'
|
||||
|
||||
floppy_ref = _prepare_floppy_image(
|
||||
floppy_ref = image_utils.prepare_floppy_image(
|
||||
task, params=ramdisk_params)
|
||||
|
||||
_eject_vmedia(task, sushy.VIRTUAL_MEDIA_FLOPPY)
|
||||
@ -892,7 +443,8 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
||||
|
||||
mode = deploy_utils.rescue_or_deploy_mode(node)
|
||||
|
||||
iso_ref = _prepare_deploy_iso(task, ramdisk_params, mode)
|
||||
iso_ref = image_utils.prepare_deploy_iso(task, ramdisk_params,
|
||||
mode, d_info)
|
||||
|
||||
_eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
|
||||
_insert_vmedia(task, iso_ref, sushy.VIRTUAL_MEDIA_CD)
|
||||
@ -922,13 +474,13 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
||||
"%(node)s", {'node': task.node.uuid})
|
||||
|
||||
_eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
|
||||
_cleanup_iso_image(task)
|
||||
image_utils.cleanup_iso_image(task)
|
||||
|
||||
if (config_via_floppy
|
||||
and _has_vmedia_device(task, sushy.VIRTUAL_MEDIA_FLOPPY)):
|
||||
_eject_vmedia(task, sushy.VIRTUAL_MEDIA_FLOPPY)
|
||||
|
||||
_cleanup_floppy_image(task)
|
||||
image_utils.cleanup_floppy_image(task)
|
||||
|
||||
def prepare_instance(self, task):
|
||||
"""Prepares the boot of instance over virtual media.
|
||||
@ -978,7 +530,8 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
||||
|
||||
params.update(root_uuid=root_uuid)
|
||||
|
||||
iso_ref = _prepare_boot_iso(task, **params)
|
||||
deploy_info = _parse_deploy_info(node)
|
||||
iso_ref = image_utils.prepare_boot_iso(task, deploy_info, **params)
|
||||
_eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
|
||||
_insert_vmedia(task, iso_ref, sushy.VIRTUAL_MEDIA_CD)
|
||||
|
||||
@ -1008,7 +561,7 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
||||
if config_via_floppy:
|
||||
_eject_vmedia(task, sushy.VIRTUAL_MEDIA_FLOPPY)
|
||||
|
||||
_cleanup_iso_image(task)
|
||||
image_utils.cleanup_iso_image(task)
|
||||
|
||||
@classmethod
|
||||
def _set_boot_device(cls, task, device, persistent=False):
|
||||
|
@ -13,18 +13,17 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
from unittest import mock
|
||||
|
||||
from oslo_utils import importutils
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import exception
|
||||
from ironic.common import images
|
||||
from ironic.common import states
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.drivers.modules import boot_mode_utils
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
from ironic.drivers.modules import image_utils
|
||||
from ironic.drivers.modules.redfish import boot as redfish_boot
|
||||
from ironic.drivers.modules.redfish import utils as redfish_utils
|
||||
from ironic.tests.unit.db import base as db_base
|
||||
@ -177,356 +176,6 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
redfish_boot._parse_deploy_info,
|
||||
task.node)
|
||||
|
||||
def test__append_filename_param_without_qs(self):
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
mock_swift_api = mock_swift.SwiftAPI.return_value
|
||||
mock_swift_api.get_temp_url.return_value = 'https://a.b/c.f?e=f'
|
||||
|
||||
url = redfish_boot._publish_image('file.iso', 'boot.iso')
|
||||
|
||||
self.assertEqual(
|
||||
'https://a.b/c.f?e=f&filename=file.iso', url)
|
||||
|
||||
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.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):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
object_name = 'image-%s' % task.node.uuid
|
||||
|
||||
redfish_boot._unpublish_image(object_name)
|
||||
|
||||
mock_swift.SwiftAPI.assert_called_once_with()
|
||||
mock_swift_api = mock_swift.SwiftAPI.return_value
|
||||
|
||||
mock_swift_api.delete_object.assert_called_once_with(
|
||||
'ironic_redfish_container', object_name)
|
||||
|
||||
@mock.patch.object(os, 'chmod', autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'shutil', autospec=True)
|
||||
@mock.patch.object(os, 'link', autospec=True)
|
||||
@mock.patch.object(os, 'mkdir', autospec=True)
|
||||
def test__publish_image_local_link(
|
||||
self, mock_mkdir, mock_link, mock_shutil, mock_chmod):
|
||||
self.config(use_swift=False, group='redfish')
|
||||
self.config(http_url='http://localhost', group='deploy')
|
||||
|
||||
url = redfish_boot._publish_image('file.iso', 'boot.iso')
|
||||
|
||||
self.assertEqual(
|
||||
'http://localhost/redfish/boot.iso?filename=file.iso', url)
|
||||
|
||||
mock_mkdir.assert_called_once_with('/httpboot/redfish', 0o755)
|
||||
mock_link.assert_called_once_with(
|
||||
'file.iso', '/httpboot/redfish/boot.iso')
|
||||
mock_chmod.assert_called_once_with('file.iso', 0o644)
|
||||
|
||||
@mock.patch.object(os, 'chmod', autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'shutil', autospec=True)
|
||||
@mock.patch.object(os, 'link', autospec=True)
|
||||
@mock.patch.object(os, 'mkdir', autospec=True)
|
||||
def test__publish_image_local_copy(
|
||||
self, mock_mkdir, mock_link, mock_shutil, mock_chmod):
|
||||
self.config(use_swift=False, group='redfish')
|
||||
self.config(http_url='http://localhost', group='deploy')
|
||||
|
||||
mock_link.side_effect = OSError()
|
||||
|
||||
url = redfish_boot._publish_image('file.iso', 'boot.iso')
|
||||
|
||||
self.assertEqual(
|
||||
'http://localhost/redfish/boot.iso?filename=file.iso', url)
|
||||
|
||||
mock_mkdir.assert_called_once_with('/httpboot/redfish', 0o755)
|
||||
|
||||
mock_shutil.copyfile.assert_called_once_with(
|
||||
'file.iso', '/httpboot/redfish/boot.iso')
|
||||
mock_chmod.assert_called_once_with('/httpboot/redfish/boot.iso',
|
||||
0o644)
|
||||
|
||||
@mock.patch.object(redfish_boot, 'ironic_utils', autospec=True)
|
||||
def test__unpublish_image_local(self, mock_ironic_utils):
|
||||
self.config(use_swift=False, group='redfish')
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
object_name = 'image-%s' % task.node.uuid
|
||||
|
||||
expected_file = '/httpboot/redfish/' + 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, '_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:
|
||||
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, '_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):
|
||||
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 = redfish_boot._prepare_floppy_image(task)
|
||||
|
||||
object_name = 'image-%s' % task.node.uuid
|
||||
|
||||
mock__publish_image.assert_called_once_with(
|
||||
mock.ANY, object_name)
|
||||
|
||||
mock_create_vfat_image.assert_called_once_with(
|
||||
mock.ANY, parameters=mock.ANY)
|
||||
|
||||
self.assertEqual(expected_url, url)
|
||||
|
||||
@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:
|
||||
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, '_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 = redfish_boot._prepare_iso_image(
|
||||
task, 'http://kernel/img', 'http://ramdisk/img',
|
||||
'http://bootloader/img', root_uuid=task.node.uuid,
|
||||
base_iso=None)
|
||||
|
||||
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',
|
||||
base_iso=None)
|
||||
|
||||
self.assertEqual(expected_url, url)
|
||||
|
||||
@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):
|
||||
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 = redfish_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',
|
||||
base_iso=None)
|
||||
|
||||
self.assertEqual(expected_url, url)
|
||||
|
||||
@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):
|
||||
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)
|
||||
|
||||
redfish_boot._prepare_iso_image(
|
||||
task, 'http://kernel/img', 'http://ramdisk/img',
|
||||
bootloader_href=None, root_uuid=task.node.uuid,
|
||||
base_iso=None)
|
||||
|
||||
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',
|
||||
base_iso=None)
|
||||
|
||||
@mock.patch.object(redfish_boot, '_publish_image', autospec=True)
|
||||
@mock.patch.object(images, 'create_boot_iso', autospec=True)
|
||||
def test__prepare_iso_image_boot_iso(
|
||||
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 = {'boot_iso': 'http://host/boot.iso',
|
||||
'capabilities': {
|
||||
'boot_option': 'ramdisk'}}
|
||||
|
||||
redfish_boot._prepare_iso_image(
|
||||
task, None, None, root_uuid=None,
|
||||
base_iso='http://host/boot.iso')
|
||||
|
||||
mock_create_boot_iso.assert_called_once_with(
|
||||
mock.ANY, mock.ANY, None, None,
|
||||
boot_mode=None, esp_image_href=None,
|
||||
configdrive_href=None,
|
||||
kernel_params=None,
|
||||
root_uuid=None,
|
||||
base_iso='http://host/boot.iso')
|
||||
|
||||
@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:
|
||||
|
||||
task.node.driver_info.update(
|
||||
{'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'}
|
||||
)
|
||||
|
||||
task.node.instance_info.update(deploy_boot_mode='uefi')
|
||||
|
||||
redfish_boot._prepare_deploy_iso(task, {}, 'deploy')
|
||||
|
||||
mock__prepare_iso_image.assert_called_once_with(
|
||||
task, 'kernel', 'ramdisk', 'bootloader', params={})
|
||||
|
||||
@mock.patch.object(redfish_boot, '_prepare_iso_image', autospec=True)
|
||||
@mock.patch.object(images, 'create_vfat_image', autospec=True)
|
||||
def test__prepare_deploy_iso_network_data(
|
||||
self, mock_create_vfat_image, 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'}
|
||||
)
|
||||
|
||||
task.node.instance_info.update()
|
||||
|
||||
network_data = {'a': ['b']}
|
||||
|
||||
mock_get_node_nw_data = mock.MagicMock(return_value=network_data)
|
||||
task.driver.network.get_node_network_data = mock_get_node_nw_data
|
||||
|
||||
redfish_boot._prepare_deploy_iso(task, {}, 'deploy')
|
||||
|
||||
mock_create_vfat_image.assert_called_once_with(
|
||||
mock.ANY, mock.ANY)
|
||||
|
||||
mock__prepare_iso_image.assert_called_once_with(
|
||||
task, 'kernel', 'ramdisk', bootloader_href=None,
|
||||
configdrive=mock.ANY, params={})
|
||||
|
||||
@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):
|
||||
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(
|
||||
{'image_source': 'http://boot/iso',
|
||||
'kernel': 'http://kernel/img',
|
||||
'ramdisk': 'http://ramdisk/img'})
|
||||
|
||||
redfish_boot._prepare_boot_iso(
|
||||
task, root_uuid=task.node.uuid)
|
||||
|
||||
mock__prepare_iso_image.assert_called_once_with(
|
||||
mock.ANY, 'http://kernel/img', 'http://ramdisk/img',
|
||||
'bootloader', root_uuid=task.node.uuid, base_iso=None)
|
||||
|
||||
@mock.patch.object(redfish_boot, '_prepare_iso_image', autospec=True)
|
||||
@mock.patch.object(images, 'create_boot_iso', autospec=True)
|
||||
def test__prepare_boot_iso_user_supplied(self, mock_create_boot_iso,
|
||||
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(
|
||||
{'boot_iso': 'http://boot/iso'})
|
||||
|
||||
redfish_boot._prepare_boot_iso(
|
||||
task, root_uuid=task.node.uuid)
|
||||
|
||||
mock__prepare_iso_image.assert_called_once_with(
|
||||
mock.ANY, None, None,
|
||||
'bootloader', root_uuid=task.node.uuid,
|
||||
base_iso='http://boot/iso')
|
||||
|
||||
@mock.patch.object(redfish_utils, 'parse_driver_info', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'validate_image_properties',
|
||||
autospec=True)
|
||||
@ -676,7 +325,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
|
||||
@mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device',
|
||||
autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_prepare_deploy_iso', autospec=True)
|
||||
@mock.patch.object(image_utils, '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)
|
||||
@ -686,14 +335,14 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
def test_prepare_ramdisk_with_params(
|
||||
self, mock_boot_mode_utils, mock_node_power_action,
|
||||
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,
|
||||
shared=False) as task:
|
||||
task.node.provision_state = states.DEPLOYING
|
||||
|
||||
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, {})
|
||||
|
||||
@ -712,8 +361,8 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
'ipa-debug': '1',
|
||||
}
|
||||
|
||||
mock__prepare_deploy_iso.assert_called_once_with(
|
||||
task, expected_params, 'deploy')
|
||||
mock_prepare_deploy_iso.assert_called_once_with(
|
||||
task, expected_params, 'deploy', {})
|
||||
|
||||
mock_node_set_boot_device.assert_called_once_with(
|
||||
task, boot_devices.CDROM, False)
|
||||
@ -722,7 +371,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
|
||||
@mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device',
|
||||
autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_prepare_deploy_iso', autospec=True)
|
||||
@mock.patch.object(image_utils, '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)
|
||||
@ -732,14 +381,14 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
def test_prepare_ramdisk_no_debug(
|
||||
self, mock_boot_mode_utils, mock_node_power_action,
|
||||
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)
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.provision_state = states.DEPLOYING
|
||||
|
||||
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, {})
|
||||
|
||||
@ -757,8 +406,8 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
'ipa-agent-token': mock.ANY,
|
||||
}
|
||||
|
||||
mock__prepare_deploy_iso.assert_called_once_with(
|
||||
task, expected_params, 'deploy')
|
||||
mock_prepare_deploy_iso.assert_called_once_with(
|
||||
task, expected_params, 'deploy', {})
|
||||
|
||||
mock_node_set_boot_device.assert_called_once_with(
|
||||
task, boot_devices.CDROM, False)
|
||||
@ -767,8 +416,8 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
|
||||
@mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device',
|
||||
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(image_utils, 'prepare_floppy_image', autospec=True)
|
||||
@mock.patch.object(image_utils, '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)
|
||||
@ -779,20 +428,22 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
def test_prepare_ramdisk_with_floppy(
|
||||
self, mock_boot_mode_utils, mock_node_power_action,
|
||||
mock__parse_driver_info, mock__insert_vmedia, mock__eject_vmedia,
|
||||
mock__has_vmedia_device, mock__prepare_deploy_iso,
|
||||
mock__prepare_floppy_image, mock_node_set_boot_device):
|
||||
mock__has_vmedia_device, mock_prepare_deploy_iso,
|
||||
mock_prepare_floppy_image, mock_node_set_boot_device):
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.node.provision_state = states.DEPLOYING
|
||||
|
||||
mock__parse_driver_info.return_value = {
|
||||
d_info = {
|
||||
'config_via_floppy': True
|
||||
}
|
||||
|
||||
mock__parse_driver_info.return_value = d_info
|
||||
|
||||
mock__has_vmedia_device.return_value = True
|
||||
mock__prepare_floppy_image.return_value = 'floppy-image-url'
|
||||
mock__prepare_deploy_iso.return_value = 'cd-image-url'
|
||||
mock_prepare_floppy_image.return_value = 'floppy-image-url'
|
||||
mock_prepare_deploy_iso.return_value = 'cd-image-url'
|
||||
|
||||
task.driver.boot.prepare_ramdisk(task, {})
|
||||
|
||||
@ -825,8 +476,8 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
'ipa-agent-token': mock.ANY,
|
||||
}
|
||||
|
||||
mock__prepare_deploy_iso.assert_called_once_with(
|
||||
task, expected_params, 'deploy')
|
||||
mock_prepare_deploy_iso.assert_called_once_with(
|
||||
task, expected_params, 'deploy', d_info)
|
||||
|
||||
mock_node_set_boot_device.assert_called_once_with(
|
||||
task, boot_devices.CDROM, False)
|
||||
@ -835,12 +486,12 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
|
||||
@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(image_utils, 'cleanup_iso_image', autospec=True)
|
||||
@mock.patch.object(image_utils, '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,
|
||||
self, mock__parse_driver_info, mock_cleanup_floppy_image,
|
||||
mock_cleanup_iso_image, mock__eject_vmedia,
|
||||
mock__has_vmedia_device):
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
@ -852,9 +503,9 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
|
||||
task.driver.boot.clean_up_ramdisk(task)
|
||||
|
||||
mock__cleanup_iso_image.assert_called_once_with(task)
|
||||
mock_cleanup_iso_image.assert_called_once_with(task)
|
||||
|
||||
mock__cleanup_floppy_image.assert_called_once_with(task)
|
||||
mock_cleanup_floppy_image.assert_called_once_with(task)
|
||||
|
||||
mock__has_vmedia_device.assert_called_once_with(
|
||||
task, sushy.VIRTUAL_MEDIA_FLOPPY)
|
||||
@ -868,17 +519,17 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
|
||||
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
|
||||
'clean_up_instance', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_prepare_boot_iso', autospec=True)
|
||||
@mock.patch.object(image_utils, '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, '_parse_deploy_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)
|
||||
def test_prepare_instance_normal_boot(
|
||||
self, mock_boot_mode_utils, mock_deploy_utils, mock_manager_utils,
|
||||
mock__parse_driver_info, mock__insert_vmedia, mock__eject_vmedia,
|
||||
mock__prepare_boot_iso, mock_clean_up_instance):
|
||||
mock__parse_deploy_info, mock__insert_vmedia, mock__eject_vmedia,
|
||||
mock_prepare_boot_iso, mock_clean_up_instance):
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
@ -888,8 +539,15 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
|
||||
mock_deploy_utils.get_boot_option.return_value = 'net'
|
||||
|
||||
mock__parse_driver_info.return_value = {}
|
||||
mock__prepare_boot_iso.return_value = 'image-url'
|
||||
d_info = {
|
||||
'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'
|
||||
}
|
||||
|
||||
mock__parse_deploy_info.return_value = d_info
|
||||
|
||||
mock_prepare_boot_iso.return_value = 'image-url'
|
||||
|
||||
task.driver.boot.prepare_instance(task)
|
||||
|
||||
@ -897,8 +555,8 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
'root_uuid': self.node.uuid
|
||||
}
|
||||
|
||||
mock__prepare_boot_iso.assert_called_once_with(
|
||||
task, **expected_params)
|
||||
mock_prepare_boot_iso.assert_called_once_with(
|
||||
task, d_info, **expected_params)
|
||||
|
||||
mock__eject_vmedia.assert_called_once_with(
|
||||
task, sushy.VIRTUAL_MEDIA_CD)
|
||||
@ -913,17 +571,17 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
|
||||
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
|
||||
'clean_up_instance', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_prepare_boot_iso', autospec=True)
|
||||
@mock.patch.object(image_utils, '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, '_parse_deploy_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)
|
||||
def test_prepare_instance_ramdisk_boot(
|
||||
self, mock_boot_mode_utils, mock_deploy_utils, mock_manager_utils,
|
||||
mock__parse_driver_info, mock__insert_vmedia, mock__eject_vmedia,
|
||||
mock__prepare_boot_iso, mock_clean_up_instance):
|
||||
mock__parse_deploy_info, mock__insert_vmedia, mock__eject_vmedia,
|
||||
mock_prepare_boot_iso, mock_clean_up_instance):
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
@ -933,11 +591,20 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
|
||||
mock_deploy_utils.get_boot_option.return_value = 'ramdisk'
|
||||
|
||||
mock__prepare_boot_iso.return_value = 'image-url'
|
||||
d_info = {
|
||||
'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'
|
||||
}
|
||||
mock__parse_deploy_info.return_value = d_info
|
||||
|
||||
mock_prepare_boot_iso.return_value = 'image-url'
|
||||
|
||||
task.driver.boot.prepare_instance(task)
|
||||
|
||||
mock__prepare_boot_iso.assert_called_once_with(task)
|
||||
mock_clean_up_instance.assert_called_once_with(mock.ANY, task)
|
||||
|
||||
mock_prepare_boot_iso.assert_called_once_with(task, d_info)
|
||||
|
||||
mock__eject_vmedia.assert_called_once_with(
|
||||
task, sushy.VIRTUAL_MEDIA_CD)
|
||||
@ -952,32 +619,38 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
|
||||
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
|
||||
'clean_up_instance', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_prepare_boot_iso', autospec=True)
|
||||
@mock.patch.object(image_utils, '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, '_parse_deploy_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)
|
||||
def test_prepare_instance_ramdisk_boot_iso(
|
||||
self, mock_boot_mode_utils, mock_deploy_utils, mock_manager_utils,
|
||||
mock__parse_driver_info, mock__insert_vmedia, mock__eject_vmedia,
|
||||
mock__prepare_boot_iso, mock_clean_up_instance):
|
||||
mock__parse_deploy_info, mock__insert_vmedia, mock__eject_vmedia,
|
||||
mock_prepare_boot_iso, mock_clean_up_instance):
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.provision_state = states.DEPLOYING
|
||||
task.node.driver_internal_info[
|
||||
'root_uuid_or_disk_id'] = self.node.uuid
|
||||
task.node.instance_info = {'boot_iso': 'http://host/boot.iso'}
|
||||
|
||||
mock_deploy_utils.get_boot_option.return_value = 'ramdisk'
|
||||
|
||||
mock__prepare_boot_iso.return_value = 'image-url'
|
||||
d_info = {
|
||||
'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'
|
||||
}
|
||||
|
||||
mock__parse_deploy_info.return_value = d_info
|
||||
mock_prepare_boot_iso.return_value = 'image-url'
|
||||
|
||||
task.driver.boot.prepare_instance(task)
|
||||
|
||||
mock__prepare_boot_iso.assert_called_once_with(task)
|
||||
mock_prepare_boot_iso.assert_called_once_with(task, d_info)
|
||||
|
||||
mock__eject_vmedia.assert_called_once_with(
|
||||
task, sushy.VIRTUAL_MEDIA_CD)
|
||||
@ -992,17 +665,17 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
|
||||
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
|
||||
'clean_up_instance', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_prepare_boot_iso', autospec=True)
|
||||
@mock.patch.object(image_utils, '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, '_parse_deploy_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)
|
||||
def test_prepare_instance_ramdisk_boot_iso_boot(
|
||||
self, mock_boot_mode_utils, mock_deploy_utils, mock_manager_utils,
|
||||
mock__parse_driver_info, mock__insert_vmedia, mock__eject_vmedia,
|
||||
mock__prepare_boot_iso, mock_clean_up_instance):
|
||||
mock__parse_deploy_info, mock__insert_vmedia, mock__eject_vmedia,
|
||||
mock_prepare_boot_iso, mock_clean_up_instance):
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
@ -1011,12 +684,13 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
i_info['boot_iso'] = "super-magic"
|
||||
task.node.instance_info = i_info
|
||||
mock_deploy_utils.get_boot_option.return_value = 'ramdisk'
|
||||
mock__parse_deploy_info.return_value = {}
|
||||
|
||||
mock__prepare_boot_iso.return_value = 'image-url'
|
||||
mock_prepare_boot_iso.return_value = 'image-url'
|
||||
|
||||
task.driver.boot.prepare_instance(task)
|
||||
|
||||
mock__prepare_boot_iso.assert_called_once_with(task)
|
||||
mock_prepare_boot_iso.assert_called_once_with(task, {})
|
||||
|
||||
mock__eject_vmedia.assert_called_once_with(
|
||||
task, sushy.VIRTUAL_MEDIA_CD)
|
||||
@ -1030,11 +704,11 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task)
|
||||
|
||||
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_cleanup_iso_image', autospec=True)
|
||||
@mock.patch.object(image_utils, 'cleanup_iso_image', autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'manager_utils', autospec=True)
|
||||
def _test_prepare_instance_local_boot(
|
||||
self, mock_manager_utils,
|
||||
mock__cleanup_iso_image, mock__eject_vmedia):
|
||||
mock_cleanup_iso_image, mock__eject_vmedia):
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
@ -1046,7 +720,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
|
||||
mock_manager_utils.node_set_boot_device.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
mock__cleanup_iso_image.assert_called_once_with(task)
|
||||
mock_cleanup_iso_image.assert_called_once_with(task)
|
||||
mock__eject_vmedia.assert_called_once_with(
|
||||
task, sushy.VIRTUAL_MEDIA_CD)
|
||||
|
||||
@ -1063,8 +737,8 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
self._test_prepare_instance_local_boot()
|
||||
|
||||
@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.patch.object(image_utils, 'cleanup_iso_image', autospec=True)
|
||||
def _test_clean_up_instance(self, mock_cleanup_iso_image,
|
||||
mock__eject_vmedia):
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
@ -1072,7 +746,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
|
||||
task.driver.boot.clean_up_instance(task)
|
||||
|
||||
mock__cleanup_iso_image.assert_called_once_with(task)
|
||||
mock_cleanup_iso_image.assert_called_once_with(task)
|
||||
eject_calls = [mock.call(task, sushy.VIRTUAL_MEDIA_CD)]
|
||||
if task.node.driver_info.get('config_via_floppy'):
|
||||
eject_calls.append(mock.call(task, sushy.VIRTUAL_MEDIA_FLOPPY))
|
||||
|
413
ironic/tests/unit/drivers/modules/test_image_utils.py
Normal file
413
ironic/tests/unit/drivers/modules/test_image_utils.py
Normal file
@ -0,0 +1,413 @@
|
||||
# Copyright 2019 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 os
|
||||
from unittest import mock
|
||||
|
||||
from oslo_utils import importutils
|
||||
|
||||
from ironic.common import images
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.drivers.modules import image_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 obj_utils
|
||||
|
||||
sushy = importutils.try_import('sushy')
|
||||
|
||||
INFO_DICT = db_utils.get_test_redfish_info()
|
||||
|
||||
|
||||
class RedfishImageHandlerTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(RedfishImageHandlerTestCase, self).setUp()
|
||||
self.config(enabled_hardware_types=['redfish'],
|
||||
enabled_power_interfaces=['redfish'],
|
||||
enabled_boot_interfaces=['redfish-virtual-media'],
|
||||
enabled_management_interfaces=['redfish'],
|
||||
enabled_inspect_interfaces=['redfish'],
|
||||
enabled_bios_interfaces=['redfish'])
|
||||
self.node = obj_utils.create_test_node(
|
||||
self.context, driver='redfish', driver_info=INFO_DICT)
|
||||
|
||||
def test__append_filename_param_without_qs(self):
|
||||
img_handler_obj = image_utils.ImageHandler(self.node.driver)
|
||||
res = img_handler_obj._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):
|
||||
img_handler_obj = image_utils.ImageHandler(self.node.driver)
|
||||
res = img_handler_obj._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):
|
||||
img_handler_obj = image_utils.ImageHandler(self.node.driver)
|
||||
res = img_handler_obj._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(image_utils, 'swift', autospec=True)
|
||||
def test_publish_image_swift(self, mock_swift):
|
||||
img_handler_obj = image_utils.ImageHandler(self.node.driver)
|
||||
mock_swift_api = mock_swift.SwiftAPI.return_value
|
||||
mock_swift_api.get_temp_url.return_value = 'https://a.b/c.f?e=f'
|
||||
|
||||
url = img_handler_obj.publish_image('file.iso', 'boot.iso')
|
||||
|
||||
self.assertEqual(
|
||||
'https://a.b/c.f?e=f&filename=file.iso', url)
|
||||
|
||||
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.get_temp_url.assert_called_once_with(
|
||||
mock.ANY, mock.ANY, mock.ANY)
|
||||
|
||||
@mock.patch.object(image_utils, 'swift', autospec=True)
|
||||
def test_unpublish_image_swift(self, mock_swift):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
img_handler_obj = image_utils.ImageHandler(self.node.driver)
|
||||
object_name = 'image-%s' % task.node.uuid
|
||||
|
||||
img_handler_obj.unpublish_image(object_name)
|
||||
|
||||
mock_swift.SwiftAPI.assert_called_once_with()
|
||||
mock_swift_api = mock_swift.SwiftAPI.return_value
|
||||
|
||||
mock_swift_api.delete_object.assert_called_once_with(
|
||||
'ironic_redfish_container', object_name)
|
||||
|
||||
@mock.patch.object(image_utils.ImageHandler, '_is_swift_enabled',
|
||||
autospec=True)
|
||||
@mock.patch.object(os, 'chmod', autospec=True)
|
||||
@mock.patch.object(image_utils, 'shutil', autospec=True)
|
||||
@mock.patch.object(os, 'link', autospec=True)
|
||||
@mock.patch.object(os, 'mkdir', autospec=True)
|
||||
def test_publish_image_local_link(
|
||||
self, mock_mkdir, mock_link, mock_shutil, mock_chmod,
|
||||
mock__is_swift):
|
||||
img_handler_obj = image_utils.ImageHandler(self.node.driver)
|
||||
mock__is_swift.return_value = False
|
||||
self.config(use_swift=False, group='redfish')
|
||||
self.config(http_url='http://localhost', group='deploy')
|
||||
|
||||
url = img_handler_obj.publish_image('file.iso', 'boot.iso')
|
||||
|
||||
self.assertEqual(
|
||||
'http://localhost/redfish/boot.iso?filename=file.iso', url)
|
||||
|
||||
mock_mkdir.assert_called_once_with('/httpboot/redfish', 0o755)
|
||||
mock_link.assert_called_once_with(
|
||||
'file.iso', '/httpboot/redfish/boot.iso')
|
||||
mock_chmod.assert_called_once_with('file.iso', 0o644)
|
||||
|
||||
@mock.patch.object(image_utils.ImageHandler, '_is_swift_enabled',
|
||||
autospec=True)
|
||||
@mock.patch.object(os, 'chmod', autospec=True)
|
||||
@mock.patch.object(image_utils, 'shutil', autospec=True)
|
||||
@mock.patch.object(os, 'link', autospec=True)
|
||||
@mock.patch.object(os, 'mkdir', autospec=True)
|
||||
def test_publish_image_local_copy(self, mock_mkdir, mock_link,
|
||||
mock_shutil, mock_chmod,
|
||||
mock__is_swift):
|
||||
mock__is_swift.return_value = False
|
||||
self.config(use_swift=False, group='redfish')
|
||||
self.config(http_url='http://localhost', group='deploy')
|
||||
img_handler_obj = image_utils.ImageHandler(self.node.driver)
|
||||
|
||||
mock_link.side_effect = OSError()
|
||||
|
||||
url = img_handler_obj.publish_image('file.iso', 'boot.iso')
|
||||
|
||||
self.assertEqual(
|
||||
'http://localhost/redfish/boot.iso?filename=file.iso', url)
|
||||
|
||||
mock_mkdir.assert_called_once_with('/httpboot/redfish', 0o755)
|
||||
|
||||
mock_shutil.copyfile.assert_called_once_with(
|
||||
'file.iso', '/httpboot/redfish/boot.iso')
|
||||
mock_chmod.assert_called_once_with('/httpboot/redfish/boot.iso',
|
||||
0o644)
|
||||
|
||||
@mock.patch.object(image_utils.ImageHandler, '_is_swift_enabled',
|
||||
autospec=True)
|
||||
@mock.patch.object(image_utils, 'ironic_utils', autospec=True)
|
||||
def test_unpublish_image_local(self, mock_ironic_utils, mock__is_swift):
|
||||
self.config(use_swift=False, group='redfish')
|
||||
mock__is_swift.return_value = False
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
img_handler_obj = image_utils.ImageHandler(self.node.driver)
|
||||
object_name = 'image-%s' % task.node.uuid
|
||||
|
||||
expected_file = '/httpboot/redfish/' + object_name
|
||||
|
||||
img_handler_obj.unpublish_image(object_name)
|
||||
|
||||
mock_ironic_utils.unlink_without_raise.assert_called_once_with(
|
||||
expected_file)
|
||||
|
||||
|
||||
class RedfishImageUtilsTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(RedfishImageUtilsTestCase, self).setUp()
|
||||
self.config(enabled_hardware_types=['redfish'],
|
||||
enabled_power_interfaces=['redfish'],
|
||||
enabled_boot_interfaces=['redfish-virtual-media'],
|
||||
enabled_management_interfaces=['redfish'],
|
||||
enabled_inspect_interfaces=['redfish'],
|
||||
enabled_bios_interfaces=['redfish'])
|
||||
self.node = obj_utils.create_test_node(
|
||||
self.context, driver='redfish', driver_info=INFO_DICT)
|
||||
|
||||
@mock.patch.object(image_utils.ImageHandler, '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:
|
||||
image_utils.cleanup_floppy_image(task)
|
||||
|
||||
object_name = 'image-%s' % task.node.uuid
|
||||
|
||||
mock_unpublish.assert_called_once_with(mock.ANY, object_name)
|
||||
|
||||
@mock.patch.object(image_utils.ImageHandler, '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):
|
||||
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 = image_utils.prepare_floppy_image(task)
|
||||
|
||||
object_name = 'image-%s' % task.node.uuid
|
||||
|
||||
mock_publish_image.assert_called_once_with(mock.ANY,
|
||||
mock.ANY, object_name)
|
||||
|
||||
mock_create_vfat_image.assert_called_once_with(
|
||||
mock.ANY, parameters=mock.ANY)
|
||||
|
||||
self.assertEqual(expected_url, url)
|
||||
|
||||
@mock.patch.object(image_utils.ImageHandler, '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:
|
||||
image_utils.cleanup_iso_image(task)
|
||||
|
||||
object_name = 'boot-%s' % task.node.uuid
|
||||
|
||||
mock_unpublish.assert_called_once_with(mock.ANY, object_name)
|
||||
|
||||
@mock.patch.object(image_utils.ImageHandler, '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 = image_utils._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, 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',
|
||||
base_iso=None)
|
||||
|
||||
self.assertEqual(expected_url, url)
|
||||
|
||||
@mock.patch.object(image_utils.ImageHandler, '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 = image_utils._prepare_iso_image(
|
||||
task, 'http://kernel/img', 'http://ramdisk/img',
|
||||
bootloader_href=None, root_uuid=task.node.uuid, base_iso=None)
|
||||
|
||||
object_name = 'boot-%s' % task.node.uuid
|
||||
|
||||
mock_publish_image.assert_called_once_with(
|
||||
mock.ANY, 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',
|
||||
base_iso=None)
|
||||
|
||||
self.assertEqual(expected_url, url)
|
||||
|
||||
@mock.patch.object(image_utils.ImageHandler, '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)
|
||||
|
||||
image_utils._prepare_iso_image(
|
||||
task, 'http://kernel/img', 'http://ramdisk/img',
|
||||
bootloader_href=None, root_uuid=task.node.uuid,
|
||||
base_iso='/path/to/baseiso')
|
||||
|
||||
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',
|
||||
base_iso='/path/to/baseiso')
|
||||
|
||||
@mock.patch.object(image_utils, '_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:
|
||||
|
||||
d_info = {
|
||||
'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'
|
||||
}
|
||||
task.node.driver_info.update(d_info)
|
||||
|
||||
task.node.instance_info.update(deploy_boot_mode='uefi')
|
||||
|
||||
image_utils.prepare_deploy_iso(task, {}, 'deploy', d_info)
|
||||
|
||||
mock__prepare_iso_image.assert_called_once_with(
|
||||
task, 'kernel', 'ramdisk', 'bootloader', params={})
|
||||
|
||||
@mock.patch.object(image_utils, '_prepare_iso_image', autospec=True)
|
||||
@mock.patch.object(images, 'create_vfat_image', autospec=True)
|
||||
def test_prepare_deploy_iso_network_data(
|
||||
self, mock_create_vfat_image, mock__prepare_iso_image):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
|
||||
d_info = {
|
||||
'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk'
|
||||
}
|
||||
task.node.driver_info.update(d_info)
|
||||
|
||||
task.node.instance_info.update()
|
||||
|
||||
network_data = {'a': ['b']}
|
||||
|
||||
mock_get_node_nw_data = mock.MagicMock(return_value=network_data)
|
||||
task.driver.network.get_node_network_data = mock_get_node_nw_data
|
||||
|
||||
image_utils.prepare_deploy_iso(task, {}, 'deploy', d_info)
|
||||
|
||||
mock_create_vfat_image.assert_called_once_with(
|
||||
mock.ANY, mock.ANY)
|
||||
|
||||
mock__prepare_iso_image.assert_called_once_with(
|
||||
task, 'kernel', 'ramdisk', bootloader_href=None,
|
||||
configdrive=mock.ANY, params={})
|
||||
|
||||
@mock.patch.object(image_utils, '_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):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
d_info = {
|
||||
'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'
|
||||
}
|
||||
task.node.driver_info.update(d_info)
|
||||
|
||||
task.node.instance_info.update(
|
||||
{'image_source': 'http://boot/iso',
|
||||
'kernel': 'http://kernel/img',
|
||||
'ramdisk': 'http://ramdisk/img'})
|
||||
|
||||
image_utils.prepare_boot_iso(
|
||||
task, d_info, root_uuid=task.node.uuid)
|
||||
|
||||
mock__prepare_iso_image.assert_called_once_with(
|
||||
mock.ANY, 'http://kernel/img', 'http://ramdisk/img',
|
||||
'bootloader', root_uuid=task.node.uuid,
|
||||
base_iso=None)
|
||||
|
||||
@mock.patch.object(image_utils, '_prepare_iso_image', autospec=True)
|
||||
@mock.patch.object(images, 'create_boot_iso', autospec=True)
|
||||
def test_prepare_boot_iso_user_supplied(self, mock_create_boot_iso,
|
||||
mock__prepare_iso_image):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
d_info = {
|
||||
'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'
|
||||
}
|
||||
task.node.driver_info.update(d_info)
|
||||
|
||||
task.node.instance_info.update(
|
||||
{'boot_iso': 'http://boot/iso'})
|
||||
|
||||
image_utils.prepare_boot_iso(
|
||||
task, d_info, root_uuid=task.node.uuid)
|
||||
|
||||
mock__prepare_iso_image.assert_called_once_with(
|
||||
mock.ANY, None, None,
|
||||
'bootloader', root_uuid=task.node.uuid,
|
||||
base_iso='http://boot/iso')
|
Loading…
Reference in New Issue
Block a user