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
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# 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_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
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
from ironic.common.glance_service import service_utils
|
from ironic.common.glance_service import service_utils
|
||||||
from ironic.common.i18n import _
|
from ironic.common.i18n import _
|
||||||
from ironic.common import images
|
|
||||||
from ironic.common import states
|
from ironic.common import states
|
||||||
from ironic.common import swift
|
|
||||||
from ironic.conductor import utils as manager_utils
|
from ironic.conductor import utils as manager_utils
|
||||||
from ironic.conf import CONF
|
from ironic.conf import CONF
|
||||||
from ironic.drivers import base
|
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 import image_utils
|
||||||
from ironic.drivers.modules.redfish import utils as redfish_utils
|
from ironic.drivers.modules.redfish import utils as redfish_utils
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
@ -151,52 +141,6 @@ def _parse_instance_info(node):
|
|||||||
return deploy_info
|
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):
|
def _insert_vmedia(task, boot_url, boot_device):
|
||||||
"""Insert bootable ISO image into virtual CD or DVD
|
"""Insert bootable ISO image into virtual CD or DVD
|
||||||
|
|
||||||
@ -283,157 +227,6 @@ def _has_vmedia_device(task, boot_device):
|
|||||||
return True
|
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):
|
def _parse_deploy_info(node):
|
||||||
"""Gets the instance and driver specific Node deployment info.
|
"""Gets the instance and driver specific Node deployment info.
|
||||||
|
|
||||||
@ -456,248 +249,6 @@ def _parse_deploy_info(node):
|
|||||||
return deploy_info
|
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):
|
class RedfishVirtualMediaBoot(base.BootInterface):
|
||||||
"""Virtual media boot interface over Redfish.
|
"""Virtual media boot interface over Redfish.
|
||||||
|
|
||||||
@ -875,7 +426,7 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
|||||||
# we tell it to
|
# we tell it to
|
||||||
ramdisk_params['boot_method'] = 'vmedia'
|
ramdisk_params['boot_method'] = 'vmedia'
|
||||||
|
|
||||||
floppy_ref = _prepare_floppy_image(
|
floppy_ref = image_utils.prepare_floppy_image(
|
||||||
task, params=ramdisk_params)
|
task, params=ramdisk_params)
|
||||||
|
|
||||||
_eject_vmedia(task, sushy.VIRTUAL_MEDIA_FLOPPY)
|
_eject_vmedia(task, sushy.VIRTUAL_MEDIA_FLOPPY)
|
||||||
@ -892,7 +443,8 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
|||||||
|
|
||||||
mode = deploy_utils.rescue_or_deploy_mode(node)
|
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)
|
_eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
|
||||||
_insert_vmedia(task, iso_ref, 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})
|
"%(node)s", {'node': task.node.uuid})
|
||||||
|
|
||||||
_eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
|
_eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
|
||||||
_cleanup_iso_image(task)
|
image_utils.cleanup_iso_image(task)
|
||||||
|
|
||||||
if (config_via_floppy
|
if (config_via_floppy
|
||||||
and _has_vmedia_device(task, sushy.VIRTUAL_MEDIA_FLOPPY)):
|
and _has_vmedia_device(task, sushy.VIRTUAL_MEDIA_FLOPPY)):
|
||||||
_eject_vmedia(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):
|
def prepare_instance(self, task):
|
||||||
"""Prepares the boot of instance over virtual media.
|
"""Prepares the boot of instance over virtual media.
|
||||||
@ -978,7 +530,8 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
|||||||
|
|
||||||
params.update(root_uuid=root_uuid)
|
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)
|
_eject_vmedia(task, sushy.VIRTUAL_MEDIA_CD)
|
||||||
_insert_vmedia(task, iso_ref, 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:
|
if config_via_floppy:
|
||||||
_eject_vmedia(task, sushy.VIRTUAL_MEDIA_FLOPPY)
|
_eject_vmedia(task, sushy.VIRTUAL_MEDIA_FLOPPY)
|
||||||
|
|
||||||
_cleanup_iso_image(task)
|
image_utils.cleanup_iso_image(task)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _set_boot_device(cls, task, device, persistent=False):
|
def _set_boot_device(cls, task, device, persistent=False):
|
||||||
|
@ -13,18 +13,17 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import os
|
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from oslo_utils import importutils
|
from oslo_utils import importutils
|
||||||
|
|
||||||
from ironic.common import boot_devices
|
from ironic.common import boot_devices
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
from ironic.common import images
|
|
||||||
from ironic.common import states
|
from ironic.common import states
|
||||||
from ironic.conductor import task_manager
|
from ironic.conductor import task_manager
|
||||||
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 import image_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.tests.unit.db import base as db_base
|
from ironic.tests.unit.db import base as db_base
|
||||||
@ -177,356 +176,6 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
redfish_boot._parse_deploy_info,
|
redfish_boot._parse_deploy_info,
|
||||||
task.node)
|
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(redfish_utils, 'parse_driver_info', autospec=True)
|
||||||
@mock.patch.object(deploy_utils, 'validate_image_properties',
|
@mock.patch.object(deploy_utils, 'validate_image_properties',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@ -676,7 +325,7 @@ 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, '_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, '_eject_vmedia', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, '_insert_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_driver_info', autospec=True)
|
||||||
@ -686,14 +335,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, {})
|
||||||
|
|
||||||
@ -712,8 +361,8 @@ 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', {})
|
||||||
|
|
||||||
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)
|
||||||
@ -722,7 +371,7 @@ 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, '_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, '_eject_vmedia', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, '_insert_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_driver_info', autospec=True)
|
||||||
@ -732,14 +381,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, {})
|
||||||
|
|
||||||
@ -757,8 +406,8 @@ 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', {})
|
||||||
|
|
||||||
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)
|
||||||
@ -767,8 +416,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, '_prepare_floppy_image', autospec=True)
|
@mock.patch.object(image_utils, 'prepare_floppy_image', 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, '_has_vmedia_device', 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, '_eject_vmedia', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, '_insert_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(
|
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,
|
||||||
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 = {
|
d_info = {
|
||||||
'config_via_floppy': True
|
'config_via_floppy': True
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mock__parse_driver_info.return_value = d_info
|
||||||
|
|
||||||
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, {})
|
||||||
|
|
||||||
@ -825,8 +476,8 @@ 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', d_info)
|
||||||
|
|
||||||
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)
|
||||||
@ -835,12 +486,12 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
@mock.patch.object(redfish_boot, '_has_vmedia_device', 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, '_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, '_cleanup_floppy_image', autospec=True)
|
@mock.patch.object(image_utils, 'cleanup_floppy_image', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, '_parse_driver_info', autospec=True)
|
@mock.patch.object(redfish_boot, '_parse_driver_info', autospec=True)
|
||||||
def test_clean_up_ramdisk(
|
def test_clean_up_ramdisk(
|
||||||
self, mock__parse_driver_info, mock__cleanup_floppy_image,
|
self, mock__parse_driver_info, mock_cleanup_floppy_image,
|
||||||
mock__cleanup_iso_image, mock__eject_vmedia,
|
mock_cleanup_iso_image, mock__eject_vmedia,
|
||||||
mock__has_vmedia_device):
|
mock__has_vmedia_device):
|
||||||
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
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)
|
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(
|
mock__has_vmedia_device.assert_called_once_with(
|
||||||
task, sushy.VIRTUAL_MEDIA_FLOPPY)
|
task, sushy.VIRTUAL_MEDIA_FLOPPY)
|
||||||
@ -868,17 +519,17 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
|
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
|
||||||
'clean_up_instance', autospec=True)
|
'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, '_eject_vmedia', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, '_insert_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, 'manager_utils', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
||||||
def test_prepare_instance_normal_boot(
|
def test_prepare_instance_normal_boot(
|
||||||
self, mock_boot_mode_utils, mock_deploy_utils, mock_manager_utils,
|
self, mock_boot_mode_utils, mock_deploy_utils, mock_manager_utils,
|
||||||
mock__parse_driver_info, mock__insert_vmedia, mock__eject_vmedia,
|
mock__parse_deploy_info, mock__insert_vmedia, mock__eject_vmedia,
|
||||||
mock__prepare_boot_iso, mock_clean_up_instance):
|
mock_prepare_boot_iso, mock_clean_up_instance):
|
||||||
|
|
||||||
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:
|
||||||
@ -888,8 +539,15 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
mock_deploy_utils.get_boot_option.return_value = 'net'
|
mock_deploy_utils.get_boot_option.return_value = 'net'
|
||||||
|
|
||||||
mock__parse_driver_info.return_value = {}
|
d_info = {
|
||||||
mock__prepare_boot_iso.return_value = 'image-url'
|
'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)
|
task.driver.boot.prepare_instance(task)
|
||||||
|
|
||||||
@ -897,8 +555,8 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
'root_uuid': self.node.uuid
|
'root_uuid': self.node.uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
mock__prepare_boot_iso.assert_called_once_with(
|
mock_prepare_boot_iso.assert_called_once_with(
|
||||||
task, **expected_params)
|
task, d_info, **expected_params)
|
||||||
|
|
||||||
mock__eject_vmedia.assert_called_once_with(
|
mock__eject_vmedia.assert_called_once_with(
|
||||||
task, sushy.VIRTUAL_MEDIA_CD)
|
task, sushy.VIRTUAL_MEDIA_CD)
|
||||||
@ -913,17 +571,17 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
|
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
|
||||||
'clean_up_instance', autospec=True)
|
'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, '_eject_vmedia', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, '_insert_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, 'manager_utils', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
||||||
def test_prepare_instance_ramdisk_boot(
|
def test_prepare_instance_ramdisk_boot(
|
||||||
self, mock_boot_mode_utils, mock_deploy_utils, mock_manager_utils,
|
self, mock_boot_mode_utils, mock_deploy_utils, mock_manager_utils,
|
||||||
mock__parse_driver_info, mock__insert_vmedia, mock__eject_vmedia,
|
mock__parse_deploy_info, mock__insert_vmedia, mock__eject_vmedia,
|
||||||
mock__prepare_boot_iso, mock_clean_up_instance):
|
mock_prepare_boot_iso, mock_clean_up_instance):
|
||||||
|
|
||||||
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:
|
||||||
@ -933,11 +591,20 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
mock_deploy_utils.get_boot_option.return_value = 'ramdisk'
|
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)
|
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(
|
mock__eject_vmedia.assert_called_once_with(
|
||||||
task, sushy.VIRTUAL_MEDIA_CD)
|
task, sushy.VIRTUAL_MEDIA_CD)
|
||||||
@ -952,32 +619,38 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
|
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
|
||||||
'clean_up_instance', autospec=True)
|
'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, '_eject_vmedia', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, '_insert_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, 'manager_utils', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
||||||
def test_prepare_instance_ramdisk_boot_iso(
|
def test_prepare_instance_ramdisk_boot_iso(
|
||||||
self, mock_boot_mode_utils, mock_deploy_utils, mock_manager_utils,
|
self, mock_boot_mode_utils, mock_deploy_utils, mock_manager_utils,
|
||||||
mock__parse_driver_info, mock__insert_vmedia, mock__eject_vmedia,
|
mock__parse_deploy_info, mock__insert_vmedia, mock__eject_vmedia,
|
||||||
mock__prepare_boot_iso, mock_clean_up_instance):
|
mock_prepare_boot_iso, mock_clean_up_instance):
|
||||||
|
|
||||||
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
|
||||||
task.node.driver_internal_info[
|
task.node.driver_internal_info[
|
||||||
'root_uuid_or_disk_id'] = self.node.uuid
|
'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_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)
|
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(
|
mock__eject_vmedia.assert_called_once_with(
|
||||||
task, sushy.VIRTUAL_MEDIA_CD)
|
task, sushy.VIRTUAL_MEDIA_CD)
|
||||||
@ -992,17 +665,17 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
|
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
|
||||||
'clean_up_instance', autospec=True)
|
'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, '_eject_vmedia', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, '_insert_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, 'manager_utils', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
||||||
def test_prepare_instance_ramdisk_boot_iso_boot(
|
def test_prepare_instance_ramdisk_boot_iso_boot(
|
||||||
self, mock_boot_mode_utils, mock_deploy_utils, mock_manager_utils,
|
self, mock_boot_mode_utils, mock_deploy_utils, mock_manager_utils,
|
||||||
mock__parse_driver_info, mock__insert_vmedia, mock__eject_vmedia,
|
mock__parse_deploy_info, mock__insert_vmedia, mock__eject_vmedia,
|
||||||
mock__prepare_boot_iso, mock_clean_up_instance):
|
mock_prepare_boot_iso, mock_clean_up_instance):
|
||||||
|
|
||||||
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:
|
||||||
@ -1011,12 +684,13 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
i_info['boot_iso'] = "super-magic"
|
i_info['boot_iso'] = "super-magic"
|
||||||
task.node.instance_info = i_info
|
task.node.instance_info = i_info
|
||||||
mock_deploy_utils.get_boot_option.return_value = 'ramdisk'
|
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)
|
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(
|
mock__eject_vmedia.assert_called_once_with(
|
||||||
task, sushy.VIRTUAL_MEDIA_CD)
|
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_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, '_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)
|
@mock.patch.object(redfish_boot, 'manager_utils', autospec=True)
|
||||||
def _test_prepare_instance_local_boot(
|
def _test_prepare_instance_local_boot(
|
||||||
self, mock_manager_utils,
|
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,
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
shared=True) as task:
|
shared=True) as task:
|
||||||
@ -1046,7 +720,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
mock_manager_utils.node_set_boot_device.assert_called_once_with(
|
mock_manager_utils.node_set_boot_device.assert_called_once_with(
|
||||||
task, boot_devices.DISK, persistent=True)
|
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(
|
mock__eject_vmedia.assert_called_once_with(
|
||||||
task, sushy.VIRTUAL_MEDIA_CD)
|
task, sushy.VIRTUAL_MEDIA_CD)
|
||||||
|
|
||||||
@ -1063,8 +737,8 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
self._test_prepare_instance_local_boot()
|
self._test_prepare_instance_local_boot()
|
||||||
|
|
||||||
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
|
@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)
|
||||||
def _test_clean_up_instance(self, mock__cleanup_iso_image,
|
def _test_clean_up_instance(self, mock_cleanup_iso_image,
|
||||||
mock__eject_vmedia):
|
mock__eject_vmedia):
|
||||||
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
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)
|
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)]
|
eject_calls = [mock.call(task, sushy.VIRTUAL_MEDIA_CD)]
|
||||||
if task.node.driver_info.get('config_via_floppy'):
|
if task.node.driver_info.get('config_via_floppy'):
|
||||||
eject_calls.append(mock.call(task, sushy.VIRTUAL_MEDIA_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