Merge "Adds support for deploying whole disk images"

This commit is contained in:
Jenkins 2015-03-13 11:00:41 +00:00 committed by Gerrit Code Review
commit dc08463ec1
20 changed files with 1311 additions and 473 deletions

View File

@ -513,6 +513,13 @@ them to Glance service:
kernel_id=$MY_VMLINUZ_UUID --property \
ramdisk_id=$MY_INITRD_UUID < my-image.qcow2
- *Note:* To deploy a whole disk image, a kernel_id and a ramdisk_id
shouldn't be associated with the image. An example is as follows::
glance image-create --name my-whole-disk-image --is-public True \
--disk-format qcow2 \
--container-format bare < my-whole-disk-image.qcow2
3. Add the deploy images to glance
Add the *my-deploy-ramdisk.kernel* and
@ -612,6 +619,19 @@ node(s) where ``ironic-conductor`` is running.
Ubuntu (14.10 and after):
sudo cp /usr/lib/PXELINUX/pxelinux.0 /tftpboot
#. If whole disk images need to be deployed via PXE-netboot, copy the
chain.c32 image to ``/tftpboot`` to support it. The chain.c32 image
might be found at::
Ubuntu (Up to and including 14.04):
sudo cp /usr/lib/syslinux/chain.c32 /tftpboot
Ubuntu (14.10 and after):
sudo cp /usr/lib/syslinux/modules/bios/chain.c32 /tftpboot
Fedora:
sudo cp /boot/extlinux/chain.c32 /tftpboot
#. If the version of syslinux is **greater than** 4 we also need to make sure
that we copy the library modules into the ``/tftpboot`` directory [2]_
[1]_::

View File

@ -6,6 +6,7 @@
iscsiadm: CommandFilter, iscsiadm, root
blkid: CommandFilter, blkid, root
blockdev: CommandFilter, blockdev, root
hexdump: CommandFilter, hexdump, root
# ironic/common/utils.py
mkswap: CommandFilter, mkswap, root

View File

@ -35,6 +35,11 @@ LOG = logging.getLogger(__name__)
IMAGE_CHUNK_SIZE = 1024 * 1024 # 1mb
CONF = cfg.CONF
# Import this opt early so that it is available when registering
# glance_opts below.
CONF.import_opt('my_ip', 'ironic.netconf')
glance_opts = [
cfg.StrOpt('glance_host',
default='$my_ip',
@ -64,8 +69,6 @@ glance_opts = [
'Set to https for SSL.'),
]
CONF = cfg.CONF
CONF.register_opts(glance_opts, group='glance')

View File

@ -27,6 +27,7 @@ from oslo_concurrency import processutils
from oslo_config import cfg
from ironic.common import exception
from ironic.common.glance_service import service_utils as glance_utils
from ironic.common.i18n import _
from ironic.common.i18n import _LE
from ironic.common import image_service as service
@ -377,3 +378,33 @@ def create_boot_iso(context, output_filename, kernel_href,
create_isolinux_image(output_filename, kernel_path,
ramdisk_path, params)
def is_whole_disk_image(ctx, instance_info):
"""Find out if the image is a partition image or a whole disk image.
:param ctx: an admin context
:param instance_info: a node's instance info dict
:returns True for whole disk images and False for partition images
and None on no image_source or Error.
"""
image_source = instance_info.get('image_source')
if not image_source:
return
is_whole_disk_image = False
if glance_utils.is_glance_image(image_source):
try:
iproperties = get_image_properties(ctx, image_source)
except Exception:
return
is_whole_disk_image = (not iproperties.get('kernel_id') and
not iproperties.get('ramdisk_id'))
else:
# Non glance image ref
if (not instance_info.get('kernel') and
not instance_info.get('ramdisk')):
is_whole_disk_image = True
return is_whole_disk_image

View File

@ -68,7 +68,9 @@ def _build_pxe_config(pxe_options, template):
env = jinja2.Environment(loader=jinja2.FileSystemLoader(tmpl_path))
template = env.get_template(tmpl_file)
return template.render({'pxe_options': pxe_options,
'ROOT': '{{ ROOT }}'})
'ROOT': '{{ ROOT }}',
'DISK_IDENTIFIER': '{{ DISK_IDENTIFIER }}',
})
def _link_mac_pxe_configs(task):

View File

@ -66,6 +66,7 @@ from ironic.common.i18n import _LC
from ironic.common.i18n import _LE
from ironic.common.i18n import _LI
from ironic.common.i18n import _LW
from ironic.common import images
from ironic.common import keystone
from ironic.common import rpc
from ironic.common import states
@ -667,14 +668,6 @@ class ConductorManager(periodic_task.PeriodicTasks):
if node.maintenance:
raise exception.NodeInMaintenance(op=_('provisioning'),
node=node.uuid)
try:
task.driver.power.validate(task)
task.driver.deploy.validate(task)
except (exception.InvalidParameterValue,
exception.MissingParameterValue) as e:
raise exception.InstanceDeployFailure(_(
"RPC do_node_deploy failed to validate deploy or "
"power info. Error: %(msg)s") % {'msg': e})
if rebuild:
event = 'rebuild'
@ -689,10 +682,29 @@ class ConductorManager(periodic_task.PeriodicTasks):
instance_info.pop('kernel', None)
instance_info.pop('ramdisk', None)
node.instance_info = instance_info
node.save()
else:
event = 'deploy'
driver_internal_info = node.driver_internal_info
# Infer the image type to make sure the deploy driver
# validates only the necessary variables for different
# image types.
# NOTE(sirushtim): The iwdi variable can be None. It's upto
# the deploy driver to validate this.
iwdi = images.is_whole_disk_image(context, node.instance_info)
driver_internal_info['is_whole_disk_image'] = iwdi
node.driver_internal_info = driver_internal_info
node.save()
try:
task.driver.power.validate(task)
task.driver.deploy.validate(task)
except (exception.InvalidParameterValue,
exception.MissingParameterValue) as e:
raise exception.InstanceDeployFailure(_(
"RPC do_node_deploy failed to validate deploy or "
"power info. Error: %(msg)s") % {'msg': e})
LOG.debug("do_node_deploy Calling event: %(event)s for node: "
"%(node)s", {'event': event, 'node': node.uuid})
try:
@ -1040,6 +1052,15 @@ class ConductorManager(periodic_task.PeriodicTasks):
node_id)
ret_dict = {}
with task_manager.acquire(context, node_id, shared=True) as task:
# NOTE(sirushtim): the is_whole_disk_image variable is needed by
# deploy drivers for doing their validate(). Since the deploy
# isn't being done yet and the driver information could change in
# the meantime, we don't know if the is_whole_disk_image value will
# change or not. It isn't saved to the DB, but only used with this
# node instance for the current validations.
iwdi = images.is_whole_disk_image(context,
task.node.instance_info)
task.node.driver_internal_info['is_whole_disk_image'] = iwdi
for iface_name in (task.driver.core_interfaces +
task.driver.standard_interfaces):
iface = getattr(task.driver, iface_name, None)

View File

@ -360,13 +360,14 @@ class BaseAgentVendor(base.VendorInterface):
on encountering error while setting the boot device on the node.
"""
node = task.node
result = self._client.install_bootloader(node, root_uuid)
if result['command_status'] == 'FAILED':
msg = (_("Failed to install a bootloader when "
"deploying node %(node)s. Error: %(error)s") %
{'node': node.uuid,
'error': result['command_error']})
self._log_and_raise_deployment_error(task, msg)
if not node.driver_internal_info.get('is_whole_disk_image'):
result = self._client.install_bootloader(node, root_uuid)
if result['command_status'] == 'FAILED':
msg = (_("Failed to install a bootloader when "
"deploying node %(node)s. Error: %(error)s") %
{'node': node.uuid,
'error': result['command_error']})
self._log_and_raise_deployment_error(task, msg)
try:
deploy_utils.try_set_boot_device(task, boot_devices.DISK)

View File

@ -15,6 +15,7 @@
import base64
import contextlib
import gzip
import math
import os
@ -185,6 +186,28 @@ def delete_iscsi(portal_address, portal_port, target_iqn):
delay_on_retry=True)
def get_disk_identifier(dev):
"""Get the disk identifier from the disk being exposed by the ramdisk.
This disk identifier is appended to the pxe config which will then be
used by chain.c32 to detect the correct disk to chainload. This is helpful
in deployments to nodes with multiple disks.
http://www.syslinux.org/wiki/index.php/Comboot/chain.c32#mbr:
:param dev: Path for the already populated disk device.
:returns The Disk Identifier.
"""
disk_identifier = utils.execute('hexdump', '-s', '440', '-n', '4',
'-e', '''\"0x%08x\"''',
dev,
run_as_root=True,
check_exit_code=[0],
attempts=5,
delay_on_retry=True)
return disk_identifier[0]
def make_partitions(dev, root_mb, swap_mb, ephemeral_mb,
configdrive_mb, commit=True, boot_option="netboot"):
"""Partition the disk device.
@ -290,28 +313,63 @@ def block_uuid(dev):
return out.strip()
def switch_pxe_config(path, root_uuid, boot_mode):
"""Switch a pxe config from deployment mode to service mode."""
def _replace_lines_in_file(path, regex_pattern, replacement):
with open(path) as f:
lines = f.readlines()
root = 'UUID=%s' % root_uuid
rre = re.compile(r'\{\{ ROOT \}\}')
if boot_mode == 'uefi':
dre = re.compile('^default=.*$')
boot_line = 'default=boot'
else:
pxe_cmd = 'goto' if CONF.pxe.ipxe_enabled else 'default'
dre = re.compile('^%s .*$' % pxe_cmd)
boot_line = '%s boot' % pxe_cmd
compiled_pattern = re.compile(regex_pattern)
with open(path, 'w') as f:
for line in lines:
line = rre.sub(root, line)
line = dre.sub(boot_line, line)
line = compiled_pattern.sub(replacement, line)
f.write(line)
def _replace_root_uuid(path, root_uuid):
root = 'UUID=%s' % root_uuid
pattern = r'\{\{ ROOT \}\}'
_replace_lines_in_file(path, pattern, root)
def _replace_boot_line(path, boot_mode, is_whole_disk_image):
if is_whole_disk_image:
boot_disk_type = 'boot_whole_disk'
else:
boot_disk_type = 'boot_partition'
if boot_mode == 'uefi':
pattern = '^default=.*$'
boot_line = 'default=%s' % boot_disk_type
else:
pxe_cmd = 'goto' if CONF.pxe.ipxe_enabled else 'default'
pattern = '^%s .*$' % pxe_cmd
boot_line = '%s %s' % (pxe_cmd, boot_disk_type)
_replace_lines_in_file(path, pattern, boot_line)
def _replace_disk_identifier(path, disk_identifier):
pattern = r'\{\{ DISK_IDENTIFIER \}\}'
_replace_lines_in_file(path, pattern, disk_identifier)
def switch_pxe_config(path, root_uuid_or_disk_id, boot_mode,
is_whole_disk_image):
"""Switch a pxe config from deployment mode to service mode.
:param path: path to the pxe config file in tftpboot.
:param root_uuid_or_disk_id: root uuid in case of partition image or
disk_id in case of whole disk image.
:param boot_mode: if boot mode is uefi or bios.
:param is_whole_disk_image: if the image is a whole disk image or not.
"""
if not is_whole_disk_image:
_replace_root_uuid(path, root_uuid_or_disk_id)
else:
_replace_disk_identifier(path, root_uuid_or_disk_id)
_replace_boot_line(path, boot_mode, is_whole_disk_image)
def notify(address, port):
"""Notify a node that it becomes ready to reboot."""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@ -480,10 +538,6 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format,
:param boot_option: Can be "local" or "netboot". "netboot" by default.
:returns: the UUID of the root partition.
"""
if not is_block_device(dev):
raise exception.InstanceDeployFailure(
_("Parent device '%s' not found") % dev)
# the only way for preserve_ephemeral to be set to true is if we are
# rebuilding an instance with --preserve_ephemeral.
commit = not preserve_ephemeral
@ -550,11 +604,11 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format,
return root_uuid
def deploy(address, port, iqn, lun, image_path,
def deploy_partition_image(address, port, iqn, lun, image_path,
root_mb, swap_mb, ephemeral_mb, ephemeral_format, node_uuid,
preserve_ephemeral=False, configdrive=None,
boot_option="netboot"):
"""All-in-one function to deploy a node.
"""All-in-one function to deploy a partition image to a node.
:param address: The iSCSI IP address.
:param port: The iSCSI port number.
@ -576,18 +630,60 @@ def deploy(address, port, iqn, lun, image_path,
:param boot_option: Can be "local" or "netboot". "netboot" by default.
:returns: the UUID of the root partition.
"""
dev = get_dev(address, port, iqn, lun)
image_mb = get_image_mb(image_path)
if image_mb > root_mb:
root_mb = image_mb
discovery(address, port)
login_iscsi(address, port, iqn)
try:
with _iscsi_setup_and_handle_errors(address, port, iqn,
lun, image_path) as dev:
image_mb = get_image_mb(image_path)
if image_mb > root_mb:
root_mb = image_mb
root_uuid = work_on_disk(dev, root_mb, swap_mb, ephemeral_mb,
ephemeral_format, image_path, node_uuid,
preserve_ephemeral=preserve_ephemeral,
configdrive=configdrive,
boot_option=boot_option)
return root_uuid
def deploy_disk_image(address, port, iqn, lun,
image_path, node_uuid):
"""All-in-one function to deploy a whole disk image to a node.
:param address: The iSCSI IP address.
:param port: The iSCSI port number.
:param iqn: The iSCSI qualified name.
:param lun: The iSCSI logical unit number.
:param image_path: Path for the instance's disk image.
:param node_uuid: node's uuid. Used for logging. Currently not in use
by this function but could be used in the future.
"""
with _iscsi_setup_and_handle_errors(address, port, iqn,
lun, image_path) as dev:
populate_image(image_path, dev)
disk_identifier = get_disk_identifier(dev)
return disk_identifier
@contextlib.contextmanager
def _iscsi_setup_and_handle_errors(address, port, iqn, lun,
image_path):
"""Function that yields an iSCSI target device to work on.
:param address: The iSCSI IP address.
:param port: The iSCSI port number.
:param iqn: The iSCSI qualified name.
:param lun: The iSCSI logical unit number.
:param image_path: Path for the instance's disk image.
"""
dev = get_dev(address, port, iqn, lun)
discovery(address, port)
login_iscsi(address, port, iqn)
if not is_block_device(dev):
raise exception.InstanceDeployFailure(_("Parent device '%s' not found")
% dev)
try:
yield dev
except processutils.ProcessExecutionError as err:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Deploy to address %s failed."), address)
@ -602,8 +698,6 @@ def deploy(address, port, iqn, lun, image_path,
logout_iscsi(address, port, iqn)
delete_iscsi(address, port, iqn)
return root_uuid
def notify_deploy_complete(address):
"""Notifies the completion of deployment to the baremetal node.

View File

@ -7,6 +7,10 @@ image={{pxe_options.deployment_aki_path}}
image={{pxe_options.aki_path}}
label=boot
label=boot_partition
initrd={{pxe_options.ari_path}}
append="root={{ ROOT }} ro text {{ pxe_options.pxe_append_params|default("", true) }} ip=%I:{{pxe_options.tftp_server}}:%G:%M:%H::on"
image=chain.c32
label=boot_whole_disk
append mbr:{{ DISK_IDENTIFIER }}

View File

@ -10,7 +10,12 @@ kernel {{ pxe_options.deployment_aki_path }} selinux=0 disk={{ pxe_options.disk
initrd {{ pxe_options.deployment_ari_path }}
boot
:boot
:boot_partition
kernel {{ pxe_options.aki_path }} root={{ ROOT }} ro text {{ pxe_options.pxe_append_params|default("", true) }}
initrd {{ pxe_options.ari_path }}
boot
:boot_whole_disk
kernel chain.c32
append mbr:{{ DISK_IDENTIFIER }}
boot

View File

@ -114,10 +114,12 @@ def parse_instance_info(node):
info = node.instance_info
i_info = {}
i_info['image_source'] = info.get('image_source')
if (i_info['image_source'] and
is_whole_disk_image = node.driver_internal_info.get('is_whole_disk_image')
if not is_whole_disk_image:
if (i_info['image_source'] and
not glance_service_utils.is_glance_image(i_info['image_source'])):
i_info['kernel'] = info.get('kernel')
i_info['ramdisk'] = info.get('ramdisk')
i_info['kernel'] = info.get('kernel')
i_info['ramdisk'] = info.get('ramdisk')
i_info['root_gb'] = info.get('root_gb')
error_msg = _("Cannot validate iSCSI deploy. Some parameters were missing"
@ -129,18 +131,26 @@ def parse_instance_info(node):
i_info['swap_mb'] = info.get('swap_mb', 0)
i_info['ephemeral_gb'] = info.get('ephemeral_gb', 0)
i_info['ephemeral_format'] = info.get('ephemeral_format')
i_info['configdrive'] = info.get('configdrive')
err_msg_invalid = _("Cannot validate parameter for iSCSI deploy. "
"Invalid parameter %(param)s. Reason: %(reason)s")
for param in ('root_gb', 'swap_mb', 'ephemeral_gb'):
try:
int(i_info[param])
except ValueError:
reason = _("'%s' is not an integer value.") % i_info[param]
reason = _("%s is not an integer value.") % i_info[param]
raise exception.InvalidParameterValue(err_msg_invalid %
{'param': param, 'reason': reason})
{'param': param,
'reason': reason})
if is_whole_disk_image:
if int(i_info['swap_mb']) > 0 or int(i_info['ephemeral_gb']) > 0:
err_msg_invalid = _("Cannot deploy whole disk image with "
"swap or ephemeral size set")
raise exception.InvalidParameterValue(err_msg_invalid)
return i_info
i_info['ephemeral_format'] = info.get('ephemeral_format')
i_info['configdrive'] = info.get('configdrive')
if i_info['ephemeral_gb'] and not i_info['ephemeral_format']:
i_info['ephemeral_format'] = CONF.pxe.default_ephemeral_format
@ -228,12 +238,15 @@ def get_deploy_info(node, **kwargs):
'iqn': kwargs.get('iqn'),
'lun': kwargs.get('lun', '1'),
'image_path': _get_image_file_path(node.uuid),
'root_mb': 1024 * int(i_info['root_gb']),
'swap_mb': int(i_info['swap_mb']),
'ephemeral_mb': 1024 * int(i_info['ephemeral_gb']),
'preserve_ephemeral': i_info['preserve_ephemeral'],
'node_uuid': node.uuid,
'boot_option': get_boot_option(node)}
'node_uuid': node.uuid}
is_whole_disk_image = node.driver_internal_info['is_whole_disk_image']
if not is_whole_disk_image:
params.update({'root_mb': 1024 * int(i_info['root_gb']),
'swap_mb': int(i_info['swap_mb']),
'ephemeral_mb': 1024 * int(i_info['ephemeral_gb']),
'preserve_ephemeral': i_info['preserve_ephemeral'],
'boot_option': get_boot_option(node)})
missing = [key for key in params if params[key] is None]
if missing:
@ -241,6 +254,9 @@ def get_deploy_info(node, **kwargs):
"Parameters %s were not passed to ironic"
" for deploy.") % missing)
if is_whole_disk_image:
return params
# configdrive and ephemeral_format are nullable
params['ephemeral_format'] = i_info.get('ephemeral_format')
params['configdrive'] = i_info.get('configdrive')
@ -258,7 +274,8 @@ def continue_deploy(task, **kwargs):
:param kwargs: the kwargs to be passed to deploy.
:raises: InvalidState if the event is not allowed by the associated
state machine.
:returns: UUID of the root partition or None on error.
:returns: UUID of the root partition for partition images or disk
identifier for whole disk images or None on error.
"""
node = task.node
@ -283,9 +300,13 @@ def continue_deploy(task, **kwargs):
LOG.debug('Continuing deployment for node %(node)s, params %(params)s',
{'node': node.uuid, 'params': log_params})
root_uuid = None
root_uuid_or_disk_id = None
try:
root_uuid = deploy_utils.deploy(**params)
if node.driver_internal_info['is_whole_disk_image']:
root_uuid_or_disk_id = deploy_utils.deploy_disk_image(**params)
else:
root_uuid_or_disk_id = deploy_utils.deploy_partition_image(
**params)
except Exception as e:
LOG.error(_LE('Deploy failed for instance %(instance)s. '
'Error: %(error)s'),
@ -294,7 +315,7 @@ def continue_deploy(task, **kwargs):
'iSCSI deployment.'))
destroy_images(node.uuid)
return root_uuid
return root_uuid_or_disk_id
def do_agent_iscsi_deploy(task, agent_client):
@ -337,21 +358,22 @@ def do_agent_iscsi_deploy(task, agent_client):
'key': iscsi_options['deployment_key'],
'address': address}
root_uuid = continue_deploy(task, **iscsi_params)
if not root_uuid:
msg = (_("Couldn't determine the UUID of the root partition "
"when deploying node %s") % node.uuid)
root_uuid_or_disk_id = continue_deploy(task, **iscsi_params)
if not root_uuid_or_disk_id:
msg = (_("Couldn't determine the UUID of the root "
"partition or the disk identifier when deploying "
"node %s") % node.uuid)
deploy_utils.set_failed_state(task, msg)
raise exception.InstanceDeployFailure(reason=msg)
# TODO(lucasagomes): Move this bit saving the root_uuid to
# iscsi_deploy.continue_deploy()
driver_internal_info = node.driver_internal_info
driver_internal_info['root_uuid'] = root_uuid
driver_internal_info['root_uuid_or_disk_id'] = root_uuid_or_disk_id
node.driver_internal_info = driver_internal_info
node.save()
return root_uuid
return root_uuid_or_disk_id
def parse_root_device_hints(node):

View File

@ -185,28 +185,34 @@ def _build_pxe_config_options(node, pxe_info, ctx):
:returns: A dictionary of pxe options to be used in the pxe bootfile
template.
"""
is_whole_disk_image = node.driver_internal_info['is_whole_disk_image']
if CONF.pxe.ipxe_enabled:
deploy_kernel = '/'.join([CONF.pxe.http_url, node.uuid,
'deploy_kernel'])
deploy_ramdisk = '/'.join([CONF.pxe.http_url, node.uuid,
'deploy_ramdisk'])
kernel = '/'.join([CONF.pxe.http_url, node.uuid, 'kernel'])
ramdisk = '/'.join([CONF.pxe.http_url, node.uuid, 'ramdisk'])
if not is_whole_disk_image:
kernel = '/'.join([CONF.pxe.http_url, node.uuid, 'kernel'])
ramdisk = '/'.join([CONF.pxe.http_url, node.uuid, 'ramdisk'])
else:
deploy_kernel = pxe_info['deploy_kernel'][1]
deploy_ramdisk = pxe_info['deploy_ramdisk'][1]
kernel = pxe_info['kernel'][1]
ramdisk = pxe_info['ramdisk'][1]
if not is_whole_disk_image:
kernel = pxe_info['kernel'][1]
ramdisk = pxe_info['ramdisk'][1]
pxe_options = {
'deployment_aki_path': deploy_kernel,
'deployment_ari_path': deploy_ramdisk,
'aki_path': kernel,
'ari_path': ramdisk,
'pxe_append_params': CONF.pxe.pxe_append_params,
'tftp_server': CONF.pxe.tftp_server
}
if not is_whole_disk_image:
pxe_options.update({'aki_path': kernel,
'ari_path': ramdisk})
deploy_ramdisk_options = iscsi_deploy.build_deploy_ramdisk_options(node)
pxe_options.update(deploy_ramdisk_options)
@ -240,7 +246,7 @@ def _cache_ramdisk_kernel(ctx, node, pxe_info):
"""Fetch the necessary kernels and ramdisks for the instance."""
fileutils.ensure_tree(
os.path.join(pxe_utils.get_root_dir(), node.uuid))
LOG.debug("Fetching kernel and ramdisk for node %s",
LOG.debug("Fetching necessary kernel and ramdisk for node %s",
node.uuid)
deploy_utils.fetch_images(ctx, TFTPImageCache(), pxe_info.values(),
CONF.force_raw_images)
@ -261,6 +267,9 @@ def _get_image_info(node, ctx):
image_info.update(pxe_utils.get_deploy_kr_info(node.uuid, d_info))
if node.driver_internal_info['is_whole_disk_image']:
return image_info
i_info = node.instance_info
labels = ('kernel', 'ramdisk')
if not (i_info.get('kernel') and i_info.get('ramdisk')):
@ -349,10 +358,13 @@ class PXEDeploy(base.DeployInterface):
iscsi_deploy.validate(task)
if service_utils.is_glance_image(d_info['image_source']):
if node.driver_internal_info.get('is_whole_disk_image'):
props = []
elif service_utils.is_glance_image(d_info['image_source']):
props = ['kernel_id', 'ramdisk_id']
else:
props = ['kernel', 'ramdisk']
iscsi_deploy.validate_image_properties(task.context, d_info, props)
@task_manager.require_exclusive_lock
@ -428,25 +440,34 @@ class PXEDeploy(base.DeployInterface):
# the image kernel and ramdisk (Or even require it).
_cache_ramdisk_kernel(task.context, task.node, pxe_info)
iwdi = task.node.driver_internal_info['is_whole_disk_image']
# NOTE(deva): prepare may be called from conductor._do_takeover
# in which case, it may need to regenerate the PXE config file for an
# already-active deployment.
if task.node.provision_state == states.ACTIVE:
# this should have been stashed when the deploy was done
# but let's guard, just in case it's missing
try:
# this should have been stashed when the deploy was done
# but let's guard, just in case it's missing
root_uuid = task.node.driver_internal_info['root_uuid']
root_uuid_or_disk_id = task.node.driver_internal_info[
'root_uuid_or_disk_id']
except KeyError:
LOG.warn(_LW("The UUID for the root partition can't be found, "
"unable to switch the pxe config from deployment "
"mode to service (boot) mode for node %(node)s"),
{"node": task.node.uuid})
if not iwdi:
LOG.warn(_LW("The UUID for the root partition can't be "
"found, unable to switch the pxe config from "
"deployment mode to service (boot) mode for node "
"%(node)s"), {"node": task.node.uuid})
else:
LOG.warn(_LW("The disk id for the whole disk image can't "
"be found, unable to switch the pxe config from "
"deployment mode to service (boot) mode for "
"node %(node)s"), {"node": task.node.uuid})
else:
pxe_config_path = pxe_utils.get_pxe_config_file_path(
task.node.uuid)
deploy_utils.switch_pxe_config(
pxe_config_path, root_uuid,
driver_utils.get_node_capability(task.node, 'boot_mode'))
pxe_config_path, root_uuid_or_disk_id,
driver_utils.get_node_capability(task.node, 'boot_mode'),
iwdi)
def clean_up(self, task):
"""Clean up the deployment environment for the task's node.
@ -529,10 +550,9 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
task.process_event('resume')
_destroy_token_file(node)
root_uuid = iscsi_deploy.continue_deploy(task, **kwargs)
if not root_uuid:
is_whole_disk_image = node.driver_internal_info['is_whole_disk_image']
root_uuid_or_disk_id = iscsi_deploy.continue_deploy(task, **kwargs)
if not root_uuid_or_disk_id:
return
# save the node's root disk UUID so that another conductor could
@ -540,7 +560,7 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
# we have to assign to node.driver_internal_info so the node knows it
# has changed.
driver_internal_info = node.driver_internal_info
driver_internal_info['root_uuid'] = root_uuid
driver_internal_info['root_uuid_or_disk_id'] = root_uuid_or_disk_id
node.driver_internal_info = driver_internal_info
node.save()
@ -552,8 +572,10 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
pxe_utils.clean_up_pxe_config(task)
else:
pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid)
deploy_utils.switch_pxe_config(pxe_config_path, root_uuid,
driver_utils.get_node_capability(node, 'boot_mode'))
node_cap = driver_utils.get_node_capability(node, 'boot_mode')
deploy_utils.switch_pxe_config(pxe_config_path,
root_uuid_or_disk_id,
node_cap, is_whole_disk_image)
deploy_utils.notify_deploy_complete(kwargs['address'])
LOG.info(_LI('Deployment to node %s done'), node.uuid)
@ -588,11 +610,13 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
# it here.
_destroy_token_file(node)
root_uuid = iscsi_deploy.do_agent_iscsi_deploy(task, self._client)
root_uuid_or_disk_id = iscsi_deploy.do_agent_iscsi_deploy(
task,
self._client)
is_whole_disk_image = node.driver_internal_info['is_whole_disk_image']
if iscsi_deploy.get_boot_option(node) == "local":
# Install the boot loader
self.configure_local_boot(task, root_uuid)
self.configure_local_boot(task, root_uuid_or_disk_id)
# If it's going to boot from the local disk, get rid of
# the PXE configuration files used for the deployment
@ -600,7 +624,9 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
else:
pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid)
boot_mode = driver_utils.get_node_capability(node, 'boot_mode')
deploy_utils.switch_pxe_config(pxe_config_path, root_uuid,
boot_mode)
deploy_utils.switch_pxe_config(
pxe_config_path,
root_uuid_or_disk_id,
boot_mode, is_whole_disk_image)
self.reboot_and_finish_deploy(task)

View File

@ -6,6 +6,11 @@ append initrd={{ pxe_options.deployment_ari_path }} selinux=0 disk={{ pxe_option
ipappend 3
label boot
label boot_partition
kernel {{ pxe_options.aki_path }}
append initrd={{ pxe_options.ari_path }} root={{ ROOT }} ro text {{ pxe_options.pxe_append_params|default("", true) }}
label boot_whole_disk
COM32 chain.c32
append mbr:{{ DISK_IDENTIFIER }}

View File

@ -31,6 +31,7 @@ from oslo_utils import uuidutils
from ironic.common import boot_devices
from ironic.common import driver_factory
from ironic.common import exception
from ironic.common import images
from ironic.common import keystone
from ironic.common import states
from ironic.common import swift
@ -922,9 +923,11 @@ class VendorPassthruTestCase(_ServiceSetUpMixin, tests_db_base.DbTestCase):
@_mock_record_keepalive
class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
@mock.patch.object(images, 'is_whole_disk_image')
class ServiceDoNodeDeployTestCase(_ServiceSetUpMixin,
tests_db_base.DbTestCase):
def test_do_node_deploy_invalid_state(self):
def test_do_node_deploy_invalid_state(self, mock_iwdi):
mock_iwdi.return_value = False
self._start_service()
# test that node deploy fails if the node is already provisioned
node = obj_utils.create_test_node(self.context, driver='fake',
@ -939,8 +942,11 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
self.assertNotIn('is_whole_disk_image', node.driver_internal_info)
def test_do_node_deploy_maintenance(self):
def test_do_node_deploy_maintenance(self, mock_iwdi):
mock_iwdi.return_value = False
node = obj_utils.create_test_node(self.context, driver='fake',
maintenance=True)
exc = self.assertRaises(messaging.rpc.ExpectedException,
@ -952,8 +958,10 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
self.assertFalse(mock_iwdi.called)
def _test_do_node_deploy_validate_fail(self, mock_validate):
def _test_do_node_deploy_validate_fail(self, mock_validate, mock_iwdi):
mock_iwdi.return_value = False
# InvalidParameterValue should be re-raised as InstanceDeployFailure
mock_validate.side_effect = exception.InvalidParameterValue('error')
node = obj_utils.create_test_node(self.context, driver='fake')
@ -966,15 +974,238 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
self.assertNotIn('is_whole_disk_image', node.driver_internal_info)
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.validate')
def test_do_node_deploy_validate_fail(self, mock_validate):
self._test_do_node_deploy_validate_fail(mock_validate)
def test_do_node_deploy_validate_fail(self, mock_validate, mock_iwdi):
self._test_do_node_deploy_validate_fail(mock_validate, mock_iwdi)
@mock.patch('ironic.drivers.modules.fake.FakePower.validate')
def test_do_node_deploy_power_validate_fail(self, mock_validate):
self._test_do_node_deploy_validate_fail(mock_validate)
def test_do_node_deploy_power_validate_fail(self, mock_validate,
mock_iwdi):
self._test_do_node_deploy_validate_fail(mock_validate, mock_iwdi)
@mock.patch('ironic.conductor.task_manager.TaskManager.process_event')
def test_deploy_with_nostate_converts_to_available(self, mock_pe,
mock_iwdi):
# expressly create a node using the Juno-era NOSTATE state
# and assert that it does not result in an error, and that the state
# is converted to the new AVAILABLE state.
# Mock the process_event call, because the transitions from
# AVAILABLE are tested thoroughly elsewhere
# NOTE(deva): This test can be deleted after Kilo is released
mock_iwdi.return_value = False
self._start_service()
node = obj_utils.create_test_node(self.context, driver='fake',
provision_state=states.NOSTATE)
self.assertEqual(states.NOSTATE, node.provision_state)
self.service.do_node_deploy(self.context, node.uuid)
self.assertTrue(mock_pe.called)
node.refresh()
self.assertEqual(states.AVAILABLE, node.provision_state)
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
self.assertFalse(node.driver_internal_info['is_whole_disk_image'])
def test_do_node_deploy_partial_ok(self, mock_iwdi):
mock_iwdi.return_value = False
self._start_service()
thread = self.service._spawn_worker(lambda: None)
with mock.patch.object(self.service, '_spawn_worker') as mock_spawn:
mock_spawn.return_value = thread
node = obj_utils.create_test_node(self.context, driver='fake',
provision_state=states.AVAILABLE)
self.service.do_node_deploy(self.context, node.uuid)
self.service._worker_pool.waitall()
node.refresh()
self.assertEqual(states.DEPLOYING, node.provision_state)
self.assertEqual(states.ACTIVE, node.target_provision_state)
# This is a sync operation last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_spawn.assert_called_once_with(mock.ANY, mock.ANY,
mock.ANY, None)
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
self.assertFalse(node.driver_internal_info['is_whole_disk_image'])
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
def test_do_node_deploy_rebuild_active_state(self, mock_deploy, mock_iwdi):
# This tests manager.do_node_deploy(), the 'else' path of
# 'if new_state == states.DEPLOYDONE'. The node's states
# aren't changed in this case.
mock_iwdi.return_value = True
self._start_service()
mock_deploy.return_value = states.DEPLOYING
node = obj_utils.create_test_node(self.context, driver='fake',
provision_state=states.ACTIVE,
target_provision_state=states.NOSTATE,
instance_info={'image_source': uuidutils.generate_uuid(),
'kernel': 'aaaa', 'ramdisk': 'bbbb'},
driver_internal_info={'is_whole_disk_image': False})
self.service.do_node_deploy(self.context, node.uuid, rebuild=True)
self.service._worker_pool.waitall()
node.refresh()
self.assertEqual(states.DEPLOYING, node.provision_state)
self.assertEqual(states.ACTIVE, node.target_provision_state)
# last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_deploy.assert_called_once_with(mock.ANY)
# Verify instance_info values has been cleared.
self.assertNotIn('kernel', node.instance_info)
self.assertNotIn('ramdisk', node.instance_info)
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
# Verify is_whole_disk_image reflects correct value on rebuild.
self.assertTrue(node.driver_internal_info['is_whole_disk_image'])
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
def test_do_node_deploy_rebuild_active_state_waiting(self, mock_deploy,
mock_iwdi):
mock_iwdi.return_value = False
self._start_service()
mock_deploy.return_value = states.DEPLOYWAIT
node = obj_utils.create_test_node(self.context, driver='fake',
provision_state=states.ACTIVE,
target_provision_state=states.NOSTATE,
instance_info={'image_source': uuidutils.generate_uuid()})
self.service.do_node_deploy(self.context, node.uuid, rebuild=True)
self.service._worker_pool.waitall()
node.refresh()
self.assertEqual(states.DEPLOYWAIT, node.provision_state)
self.assertEqual(states.ACTIVE, node.target_provision_state)
# last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_deploy.assert_called_once_with(mock.ANY)
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
self.assertFalse(node.driver_internal_info['is_whole_disk_image'])
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
def test_do_node_deploy_rebuild_active_state_done(self, mock_deploy,
mock_iwdi):
mock_iwdi.return_value = False
self._start_service()
mock_deploy.return_value = states.DEPLOYDONE
node = obj_utils.create_test_node(self.context, driver='fake',
provision_state=states.ACTIVE,
target_provision_state=states.NOSTATE)
self.service.do_node_deploy(self.context, node.uuid, rebuild=True)
self.service._worker_pool.waitall()
node.refresh()
self.assertEqual(states.ACTIVE, node.provision_state)
self.assertEqual(states.NOSTATE, node.target_provision_state)
# last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_deploy.assert_called_once_with(mock.ANY)
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
self.assertFalse(node.driver_internal_info['is_whole_disk_image'])
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
def test_do_node_deploy_rebuild_deployfail_state(self, mock_deploy,
mock_iwdi):
mock_iwdi.return_value = False
self._start_service()
mock_deploy.return_value = states.DEPLOYDONE
node = obj_utils.create_test_node(self.context, driver='fake',
provision_state=states.DEPLOYFAIL,
target_provision_state=states.NOSTATE)
self.service.do_node_deploy(self.context, node.uuid, rebuild=True)
self.service._worker_pool.waitall()
node.refresh()
self.assertEqual(states.ACTIVE, node.provision_state)
self.assertEqual(states.NOSTATE, node.target_provision_state)
# last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_deploy.assert_called_once_with(mock.ANY)
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
self.assertFalse(node.driver_internal_info['is_whole_disk_image'])
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
def test_do_node_deploy_rebuild_error_state(self, mock_deploy, mock_iwdi):
mock_iwdi.return_value = False
self._start_service()
mock_deploy.return_value = states.DEPLOYDONE
node = obj_utils.create_test_node(self.context, driver='fake',
provision_state=states.ERROR,
target_provision_state=states.NOSTATE)
self.service.do_node_deploy(self.context, node.uuid, rebuild=True)
self.service._worker_pool.waitall()
node.refresh()
self.assertEqual(states.ACTIVE, node.provision_state)
self.assertEqual(states.NOSTATE, node.target_provision_state)
# last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_deploy.assert_called_once_with(mock.ANY)
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
self.assertFalse(node.driver_internal_info['is_whole_disk_image'])
def test_do_node_deploy_rebuild_from_available_state(self, mock_iwdi):
mock_iwdi.return_value = False
self._start_service()
# test node will not rebuild if state is AVAILABLE
node = obj_utils.create_test_node(self.context, driver='fake',
provision_state=states.AVAILABLE)
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.do_node_deploy,
self.context, node['uuid'], rebuild=True)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.InvalidStateRequested, exc.exc_info[0])
# Last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
self.assertNotIn('is_whole_disk_image', node.driver_internal_info)
def test_do_node_deploy_worker_pool_full(self, mock_iwdi):
mock_iwdi.return_value = False
prv_state = states.AVAILABLE
tgt_prv_state = states.NOSTATE
node = obj_utils.create_test_node(self.context,
provision_state=prv_state,
target_provision_state=tgt_prv_state,
last_error=None, driver='fake')
self._start_service()
with mock.patch.object(self.service, '_spawn_worker') as mock_spawn:
mock_spawn.side_effect = exception.NoFreeConductorWorker()
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.do_node_deploy,
self.context, node.uuid)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.NoFreeConductorWorker, exc.exc_info[0])
self.service._worker_pool.waitall()
node.refresh()
# Make sure things were rolled back
self.assertEqual(prv_state, node.provision_state)
self.assertEqual(tgt_prv_state, node.target_provision_state)
self.assertIsNotNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
self.assertFalse(node.driver_internal_info['is_whole_disk_image'])
@_mock_record_keepalive
class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
tests_db_base.DbTestCase):
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.prepare')
def test__do_node_deploy_driver_raises_prepare_error(self, mock_prepare,
@ -1105,163 +1336,6 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
self.assertIsNone(node.last_error)
mock_deploy.assert_called_once_with(mock.ANY)
@mock.patch('ironic.conductor.task_manager.TaskManager.process_event')
def test_deploy_with_nostate_converts_to_available(self, mock_pe):
# expressly create a node using the Juno-era NOSTATE state
# and assert that it does not result in an error, and that the state
# is converted to the new AVAILABLE state.
# Mock the process_event call, because the transitions from
# AVAILABLE are tested thoroughly elsewhere
# NOTE(deva): This test can be deleted after Kilo is released
self._start_service()
node = obj_utils.create_test_node(self.context, driver='fake',
provision_state=states.NOSTATE)
self.assertEqual(states.NOSTATE, node.provision_state)
self.service.do_node_deploy(self.context, node.uuid)
self.assertTrue(mock_pe.called)
node.refresh()
self.assertEqual(states.AVAILABLE, node.provision_state)
def test_do_node_deploy_partial_ok(self):
self._start_service()
thread = self.service._spawn_worker(lambda: None)
with mock.patch.object(self.service, '_spawn_worker') as mock_spawn:
mock_spawn.return_value = thread
node = obj_utils.create_test_node(self.context, driver='fake',
provision_state=states.AVAILABLE)
self.service.do_node_deploy(self.context, node.uuid)
self.service._worker_pool.waitall()
node.refresh()
self.assertEqual(states.DEPLOYING, node.provision_state)
self.assertEqual(states.ACTIVE, node.target_provision_state)
# This is a sync operation last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_spawn.assert_called_once_with(mock.ANY, mock.ANY,
mock.ANY, None)
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
def test_do_node_deploy_rebuild_active_state(self, mock_deploy):
# This tests manager.do_node_deploy(), the 'else' path of
# 'if new_state == states.DEPLOYDONE'. The node's states
# aren't changed in this case.
self._start_service()
mock_deploy.return_value = states.DEPLOYING
node = obj_utils.create_test_node(self.context, driver='fake',
provision_state=states.ACTIVE,
target_provision_state=states.NOSTATE,
instance_info={'image_source': uuidutils.generate_uuid(),
'kernel': 'aaaa', 'ramdisk': 'bbbb'})
self.service.do_node_deploy(self.context, node.uuid, rebuild=True)
self.service._worker_pool.waitall()
node.refresh()
self.assertEqual(states.DEPLOYING, node.provision_state)
self.assertEqual(states.ACTIVE, node.target_provision_state)
# last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_deploy.assert_called_once_with(mock.ANY)
# Verify instance_info values has been cleared.
self.assertNotIn('kernel', node.instance_info)
self.assertNotIn('ramdisk', node.instance_info)
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
def test_do_node_deploy_rebuild_active_state_waiting(self, mock_deploy):
self._start_service()
mock_deploy.return_value = states.DEPLOYWAIT
node = obj_utils.create_test_node(self.context, driver='fake',
provision_state=states.ACTIVE,
target_provision_state=states.NOSTATE,
instance_info={'image_source': uuidutils.generate_uuid()})
self.service.do_node_deploy(self.context, node.uuid, rebuild=True)
self.service._worker_pool.waitall()
node.refresh()
self.assertEqual(states.DEPLOYWAIT, node.provision_state)
self.assertEqual(states.ACTIVE, node.target_provision_state)
# last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_deploy.assert_called_once_with(mock.ANY)
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
def test_do_node_deploy_rebuild_active_state_done(self, mock_deploy):
self._start_service()
mock_deploy.return_value = states.DEPLOYDONE
node = obj_utils.create_test_node(self.context, driver='fake',
provision_state=states.ACTIVE,
target_provision_state=states.NOSTATE)
self.service.do_node_deploy(self.context, node.uuid, rebuild=True)
self.service._worker_pool.waitall()
node.refresh()
self.assertEqual(states.ACTIVE, node.provision_state)
self.assertEqual(states.NOSTATE, node.target_provision_state)
# last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_deploy.assert_called_once_with(mock.ANY)
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
def test_do_node_deploy_rebuild_deployfail_state(self, mock_deploy):
self._start_service()
mock_deploy.return_value = states.DEPLOYDONE
node = obj_utils.create_test_node(self.context, driver='fake',
provision_state=states.DEPLOYFAIL,
target_provision_state=states.NOSTATE)
self.service.do_node_deploy(self.context, node.uuid, rebuild=True)
self.service._worker_pool.waitall()
node.refresh()
self.assertEqual(states.ACTIVE, node.provision_state)
self.assertEqual(states.NOSTATE, node.target_provision_state)
# last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_deploy.assert_called_once_with(mock.ANY)
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
def test_do_node_deploy_rebuild_error_state(self, mock_deploy):
self._start_service()
mock_deploy.return_value = states.DEPLOYDONE
node = obj_utils.create_test_node(self.context, driver='fake',
provision_state=states.ERROR,
target_provision_state=states.NOSTATE)
self.service.do_node_deploy(self.context, node.uuid, rebuild=True)
self.service._worker_pool.waitall()
node.refresh()
self.assertEqual(states.ACTIVE, node.provision_state)
self.assertEqual(states.NOSTATE, node.target_provision_state)
# last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_deploy.assert_called_once_with(mock.ANY)
def test_do_node_deploy_rebuild_from_available_state(self):
self._start_service()
# test node will not rebuild if state is AVAILABLE
node = obj_utils.create_test_node(self.context, driver='fake',
provision_state=states.AVAILABLE)
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.do_node_deploy,
self.context, node['uuid'], rebuild=True)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.InvalidStateRequested, exc.exc_info[0])
# Last_error should be None.
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.clean_up')
def test__check_deploy_timeouts(self, mock_cleanup):
self._start_service()
@ -1279,32 +1353,6 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
self.assertIsNotNone(node.last_error)
mock_cleanup.assert_called_once_with(mock.ANY)
def test_do_node_deploy_worker_pool_full(self):
prv_state = states.AVAILABLE
tgt_prv_state = states.NOSTATE
node = obj_utils.create_test_node(self.context,
provision_state=prv_state,
target_provision_state=tgt_prv_state,
last_error=None, driver='fake')
self._start_service()
with mock.patch.object(self.service, '_spawn_worker') as mock_spawn:
mock_spawn.side_effect = exception.NoFreeConductorWorker()
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.do_node_deploy,
self.context, node.uuid)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.NoFreeConductorWorker, exc.exc_info[0])
self.service._worker_pool.waitall()
node.refresh()
# Make sure things were rolled back
self.assertEqual(prv_state, node.provision_state)
self.assertEqual(tgt_prv_state, node.target_provision_state)
self.assertIsNotNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
def test_do_node_tear_down_invalid_state(self):
self._start_service()
# test node.provision_state is incorrect for tear_down
@ -1332,10 +1380,11 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.tear_down')
def test_do_node_tear_down_driver_raises_error(self, mock_tear_down):
# test when driver.deploy.tear_down raises exception
node = obj_utils.create_test_node(self.context, driver='fake',
provision_state=states.DELETING,
target_provision_state=states.AVAILABLE,
instance_info={'foo': 'bar'})
node = obj_utils.create_test_node(
self.context, driver='fake', provision_state=states.DELETING,
target_provision_state=states.AVAILABLE,
instance_info={'foo': 'bar'},
driver_internal_info={'is_whole_disk_image': False})
task = task_manager.TaskManager(self.context, node.uuid)
self._start_service()
@ -1354,10 +1403,11 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.tear_down')
def test__do_node_tear_down_ok(self, mock_tear_down, mock_clean):
# test when driver.deploy.tear_down succeeds
node = obj_utils.create_test_node(self.context, driver='fake',
provision_state=states.DELETING,
target_provision_state=states.AVAILABLE,
instance_info={'foo': 'bar'})
node = obj_utils.create_test_node(
self.context, driver='fake', provision_state=states.DELETING,
target_provision_state=states.AVAILABLE,
instance_info={'foo': 'bar'},
driver_internal_info={'is_whole_disk_image': False})
task = task_manager.TaskManager(self.context, node.uuid)
self._start_service()
@ -1375,10 +1425,11 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.tear_down')
def _test_do_node_tear_down_from_state(self, init_state, mock_tear_down,
mock_clean):
node = obj_utils.create_test_node(self.context, driver='fake',
uuid=uuidutils.generate_uuid(),
provision_state=init_state,
target_provision_state=states.AVAILABLE)
node = obj_utils.create_test_node(
self.context, driver='fake', uuid=uuidutils.generate_uuid(),
provision_state=init_state,
target_provision_state=states.AVAILABLE,
driver_internal_info={'is_whole_disk_image': False})
self.service.do_node_tear_down(self.context, node.uuid)
self.service._worker_pool.waitall()
@ -1410,11 +1461,12 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
prv_state = states.ACTIVE
tgt_prv_state = states.NOSTATE
fake_instance_info = {'foo': 'bar'}
node = obj_utils.create_test_node(self.context, driver='fake',
provision_state=prv_state,
target_provision_state=tgt_prv_state,
instance_info=fake_instance_info,
last_error=None)
driver_internal_info = {'is_whole_disk_image': False}
node = obj_utils.create_test_node(
self.context, driver='fake', provision_state=prv_state,
target_provision_state=tgt_prv_state,
instance_info=fake_instance_info,
driver_internal_info=driver_internal_info, last_error=None)
self._start_service()
mock_spawn.side_effect = exception.NoFreeConductorWorker()
@ -1426,8 +1478,9 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
self.assertEqual(exception.NoFreeConductorWorker, exc.exc_info[0])
self.service._worker_pool.waitall()
node.refresh()
# Assert instance_info was not touched
# Assert instance_info/driver_internal_info was not touched
self.assertEqual(fake_instance_info, node.instance_info)
self.assertEqual(driver_internal_info, node.driver_internal_info)
# Make sure things were rolled back
self.assertEqual(prv_state, node.provision_state)
self.assertEqual(tgt_prv_state, node.target_provision_state)
@ -1511,9 +1564,12 @@ class MiscTestCase(_ServiceSetUpMixin, _CommonMixIn, tests_db_base.DbTestCase):
self.assertTrue(self.service._mapped_to_this_conductor(n['uuid'],
'fake'))
self.assertFalse(self.service._mapped_to_this_conductor(n['uuid'],
'otherdriver'))
def test_validate_driver_interfaces(self):
@mock.patch.object(images, 'is_whole_disk_image')
def test_validate_driver_interfaces(self, mock_iwdi):
mock_iwdi.return_value = False
node = obj_utils.create_test_node(self.context, driver='fake')
ret = self.service.validate_driver_interfaces(self.context,
node.uuid)
@ -1523,8 +1579,11 @@ class MiscTestCase(_ServiceSetUpMixin, _CommonMixIn, tests_db_base.DbTestCase):
'management': {'result': True},
'deploy': {'result': True}}
self.assertEqual(expected, ret)
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
def test_validate_driver_interfaces_validation_fail(self):
@mock.patch.object(images, 'is_whole_disk_image')
def test_validate_driver_interfaces_validation_fail(self, mock_iwdi):
mock_iwdi.return_value = False
node = obj_utils.create_test_node(self.context, driver='fake')
with mock.patch(
'ironic.drivers.modules.fake.FakeDeploy.validate'
@ -1535,6 +1594,7 @@ class MiscTestCase(_ServiceSetUpMixin, _CommonMixIn, tests_db_base.DbTestCase):
node.uuid)
self.assertFalse(ret['deploy']['result'])
self.assertEqual(reason, ret['deploy']['reason'])
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
@mock.patch.object(manager.ConductorManager, '_mapped_to_this_conductor')
@mock.patch.object(dbapi.IMPL, 'get_nodeinfo_list')

View File

@ -69,6 +69,12 @@ def get_test_pxe_driver_info():
}
def get_test_pxe_driver_internal_info():
return {
"is_whole_disk_image": False,
}
def get_test_pxe_instance_info():
return {
"image_source": "glance://image_uuid",

View File

@ -6,6 +6,11 @@ append initrd=/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_ramdisk seli
ipappend 3
label boot
label boot_partition
kernel /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/kernel
append initrd=/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/ramdisk root={{ ROOT }} ro text test_param
label boot_whole_disk
COM32 chain.c32
append mbr:{{ DISK_IDENTIFIER }}

View File

@ -27,6 +27,7 @@ from oslo_concurrency import processutils
from oslo_config import cfg
from oslo_utils import uuidutils
import requests
import testtools
from ironic.common import boot_devices
from ironic.common import disk_partitioner
@ -51,22 +52,47 @@ kernel deploy_kernel
append initrd=deploy_ramdisk
ipappend 3
label boot
label boot_partition
kernel kernel
append initrd=ramdisk root={{ ROOT }}
label boot_whole_disk
COM32 chain.c32
append mbr:{{ DISK_IDENTIFIER }}
"""
_PXECONF_BOOT = """
default boot
_PXECONF_BOOT_PARTITION = """
default boot_partition
label deploy
kernel deploy_kernel
append initrd=deploy_ramdisk
ipappend 3
label boot
label boot_partition
kernel kernel
append initrd=ramdisk root=UUID=12345678-1234-1234-1234-1234567890abcdef
label boot_whole_disk
COM32 chain.c32
append mbr:{{ DISK_IDENTIFIER }}
"""
_PXECONF_BOOT_WHOLE_DISK = """
default boot_whole_disk
label deploy
kernel deploy_kernel
append initrd=deploy_ramdisk
ipappend 3
label boot_partition
kernel kernel
append initrd=ramdisk root={{ ROOT }}
label boot_whole_disk
COM32 chain.c32
append mbr:0x12345678
"""
_IPXECONF_DEPLOY = """
@ -81,28 +107,61 @@ kernel deploy_kernel
initrd deploy_ramdisk
boot
:boot
:boot_partition
kernel kernel
append initrd=ramdisk root={{ ROOT }}
boot
:boot_whole_disk
kernel chain.c32
append mbr:{{ DISK_IDENTIFIER }}
boot
"""
_IPXECONF_BOOT = """
_IPXECONF_BOOT_PARTITION = """
#!ipxe
dhcp
goto boot
goto boot_partition
:deploy
kernel deploy_kernel
initrd deploy_ramdisk
boot
:boot
:boot_partition
kernel kernel
append initrd=ramdisk root=UUID=12345678-1234-1234-1234-1234567890abcdef
boot
:boot_whole_disk
kernel chain.c32
append mbr:{{ DISK_IDENTIFIER }}
boot
"""
_IPXECONF_BOOT_WHOLE_DISK = """
#!ipxe
dhcp
goto boot_whole_disk
:deploy
kernel deploy_kernel
initrd deploy_ramdisk
boot
:boot_partition
kernel kernel
append initrd=ramdisk root={{ ROOT }}
boot
:boot_whole_disk
kernel chain.c32
append mbr:0x12345678
boot
"""
_UEFI_PXECONF_DEPLOY = """
@ -114,13 +173,17 @@ image=deploy_kernel
append="ro text"
image=kernel
label=boot
label=boot_partition
initrd=ramdisk
append="root={{ ROOT }}"
image=chain.c32
label=boot_whole_disk
append mbr:{{ DISK_IDENTIFIER }}
"""
_UEFI_PXECONF_BOOT = """
default=boot
_UEFI_PXECONF_BOOT_PARTITION = """
default=boot_partition
image=deploy_kernel
label=deploy
@ -128,9 +191,31 @@ image=deploy_kernel
append="ro text"
image=kernel
label=boot
label=boot_partition
initrd=ramdisk
append="root=UUID=12345678-1234-1234-1234-1234567890abcdef"
image=chain.c32
label=boot_whole_disk
append mbr:{{ DISK_IDENTIFIER }}
"""
_UEFI_PXECONF_BOOT_WHOLE_DISK = """
default=boot_whole_disk
image=deploy_kernel
label=deploy
initrd=deploy_ramdisk
append="ro text"
image=kernel
label=boot_partition
initrd=ramdisk
append="root={{ ROOT }}"
image=chain.c32
label=boot_whole_disk
append mbr:0x12345678
"""
@ -148,7 +233,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
parent_mock.attach_mock(mocker, name)
return parent_mock
def _test_deploy(self, boot_option=None):
def _test_deploy_partition_image(self, boot_option=None):
"""Check loosely all functions are called with right args."""
address = '127.0.0.1'
port = 3306
@ -179,10 +264,10 @@ class PhysicalWorkTestCase(tests_base.TestCase):
parent_mock.make_partitions.return_value = {'root': root_part,
'swap': swap_part}
calls_expected = [mock.call.get_dev(address, port, iqn, lun),
mock.call.get_image_mb(image_path),
mock.call.discovery(address, port),
mock.call.login_iscsi(address, port, iqn),
mock.call.is_block_device(dev),
mock.call.get_image_mb(image_path),
mock.call.destroy_disk_metadata(dev, node_uuid)]
if boot_option:
@ -207,24 +292,26 @@ class PhysicalWorkTestCase(tests_base.TestCase):
if boot_option:
kwargs = {'boot_option': boot_option}
returned_root_uuid = utils.deploy(address, port, iqn, lun,
image_path, root_mb, swap_mb,
ephemeral_mb, ephemeral_format,
node_uuid, **kwargs)
returned_root_uuid = utils.deploy_partition_image(address, port, iqn,
lun, image_path,
root_mb, swap_mb,
ephemeral_mb,
ephemeral_format,
node_uuid, **kwargs)
self.assertEqual(calls_expected, parent_mock.mock_calls)
self.assertEqual(root_uuid, returned_root_uuid)
def test_deploy_without_boot_option(self):
self._test_deploy()
def test_deploy_partition_image_without_boot_option(self):
self._test_deploy_partition_image()
def test_deploy_netboot(self):
self._test_deploy(boot_option="netboot")
def test_deploy_partition_image_netboot(self):
self._test_deploy_partition_image(boot_option="netboot")
def test_deploy_localboot(self):
self._test_deploy(boot_option="local")
def test_deploy_partition_image_localboot(self):
self._test_deploy_partition_image(boot_option="local")
def test_deploy_without_swap(self):
def test_deploy_partition_image_without_swap(self):
"""Check loosely all functions are called with right args."""
address = '127.0.0.1'
port = 3306
@ -253,10 +340,10 @@ class PhysicalWorkTestCase(tests_base.TestCase):
parent_mock.block_uuid.return_value = root_uuid
parent_mock.make_partitions.return_value = {'root': root_part}
calls_expected = [mock.call.get_dev(address, port, iqn, lun),
mock.call.get_image_mb(image_path),
mock.call.discovery(address, port),
mock.call.login_iscsi(address, port, iqn),
mock.call.is_block_device(dev),
mock.call.get_image_mb(image_path),
mock.call.destroy_disk_metadata(dev, node_uuid),
mock.call.make_partitions(dev, root_mb, swap_mb,
ephemeral_mb,
@ -269,15 +356,17 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock.call.logout_iscsi(address, port, iqn),
mock.call.delete_iscsi(address, port, iqn)]
returned_root_uuid = utils.deploy(address, port, iqn, lun,
image_path, root_mb, swap_mb,
ephemeral_mb, ephemeral_format,
node_uuid)
returned_root_uuid = utils.deploy_partition_image(address, port, iqn,
lun, image_path,
root_mb, swap_mb,
ephemeral_mb,
ephemeral_format,
node_uuid)
self.assertEqual(calls_expected, parent_mock.mock_calls)
self.assertEqual(root_uuid, returned_root_uuid)
def test_deploy_with_ephemeral(self):
def test_deploy_partition_image_with_ephemeral(self):
"""Check loosely all functions are called with right args."""
address = '127.0.0.1'
port = 3306
@ -311,10 +400,10 @@ class PhysicalWorkTestCase(tests_base.TestCase):
'ephemeral': ephemeral_part,
'root': root_part}
calls_expected = [mock.call.get_dev(address, port, iqn, lun),
mock.call.get_image_mb(image_path),
mock.call.discovery(address, port),
mock.call.login_iscsi(address, port, iqn),
mock.call.is_block_device(dev),
mock.call.get_image_mb(image_path),
mock.call.destroy_disk_metadata(dev, node_uuid),
mock.call.make_partitions(dev, root_mb, swap_mb,
ephemeral_mb,
@ -332,15 +421,17 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock.call.logout_iscsi(address, port, iqn),
mock.call.delete_iscsi(address, port, iqn)]
returned_root_uuid = utils.deploy(address, port, iqn, lun,
image_path, root_mb, swap_mb,
ephemeral_mb, ephemeral_format,
node_uuid)
returned_root_uuid = utils.deploy_partition_image(address, port, iqn,
lun, image_path,
root_mb, swap_mb,
ephemeral_mb,
ephemeral_format,
node_uuid)
self.assertEqual(calls_expected, parent_mock.mock_calls)
self.assertEqual(root_uuid, returned_root_uuid)
def test_deploy_preserve_ephemeral(self):
def test_deploy_partition_image_preserve_ephemeral(self):
"""Check if all functions are called with right args."""
address = '127.0.0.1'
port = 3306
@ -375,10 +466,10 @@ class PhysicalWorkTestCase(tests_base.TestCase):
'root': root_part}
parent_mock.block_uuid.return_value = root_uuid
calls_expected = [mock.call.get_dev(address, port, iqn, lun),
mock.call.get_image_mb(image_path),
mock.call.discovery(address, port),
mock.call.login_iscsi(address, port, iqn),
mock.call.is_block_device(dev),
mock.call.get_image_mb(image_path),
mock.call.make_partitions(dev, root_mb, swap_mb,
ephemeral_mb,
configdrive_mb,
@ -393,18 +484,21 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock.call.logout_iscsi(address, port, iqn),
mock.call.delete_iscsi(address, port, iqn)]
returned_root_uuid = utils.deploy(address, port, iqn, lun,
image_path, root_mb, swap_mb,
ephemeral_mb, ephemeral_format,
node_uuid, preserve_ephemeral=True,
boot_option="netboot")
returned_root_uuid = utils.deploy_partition_image(address, port, iqn,
lun, image_path,
root_mb, swap_mb,
ephemeral_mb,
ephemeral_format,
node_uuid,
preserve_ephemeral=True,
boot_option="netboot")
self.assertEqual(calls_expected, parent_mock.mock_calls)
self.assertFalse(parent_mock.mkfs_ephemeral.called)
self.assertFalse(parent_mock.get_dev_block_size.called)
self.assertEqual(root_uuid, returned_root_uuid)
@mock.patch.object(common_utils, 'unlink_without_raise')
def test_deploy_with_configdrive(self, mock_unlink):
def test_deploy_partition_image_with_configdrive(self, mock_unlink):
"""Check loosely all functions are called with right args."""
address = '127.0.0.1'
port = 3306
@ -439,10 +533,10 @@ class PhysicalWorkTestCase(tests_base.TestCase):
configdrive_part}
parent_mock._get_configdrive.return_value = (10, 'configdrive-path')
calls_expected = [mock.call.get_dev(address, port, iqn, lun),
mock.call.get_image_mb(image_path),
mock.call.discovery(address, port),
mock.call.login_iscsi(address, port, iqn),
mock.call.is_block_device(dev),
mock.call.get_image_mb(image_path),
mock.call.destroy_disk_metadata(dev, node_uuid),
mock.call._get_configdrive(configdrive_url,
node_uuid),
@ -459,16 +553,49 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock.call.logout_iscsi(address, port, iqn),
mock.call.delete_iscsi(address, port, iqn)]
returned_root_uuid = utils.deploy(address, port, iqn, lun,
image_path, root_mb, swap_mb,
ephemeral_mb, ephemeral_format,
node_uuid,
configdrive=configdrive_url)
returned_root_uuid = utils.deploy_partition_image(address, port, iqn,
lun, image_path,
root_mb, swap_mb,
ephemeral_mb,
ephemeral_format,
node_uuid,
configdrive=configdrive_url)
self.assertEqual(calls_expected, parent_mock.mock_calls)
self.assertEqual(root_uuid, returned_root_uuid)
mock_unlink.assert_called_once_with('configdrive-path')
@mock.patch.object(utils, 'get_disk_identifier')
def test_deploy_whole_disk_image(self, mock_gdi):
"""Check loosely all functions are called with right args."""
address = '127.0.0.1'
port = 3306
iqn = 'iqn.xyz'
lun = 1
image_path = '/tmp/xyz/image'
node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
dev = '/dev/fake'
name_list = ['get_dev', 'discovery', 'login_iscsi', 'logout_iscsi',
'delete_iscsi', 'is_block_device', 'populate_image',
'notify']
parent_mock = self._mock_calls(name_list)
parent_mock.get_dev.return_value = dev
parent_mock.is_block_device.return_value = True
mock_gdi.return_value = '0x12345678'
calls_expected = [mock.call.get_dev(address, port, iqn, lun),
mock.call.discovery(address, port),
mock.call.login_iscsi(address, port, iqn),
mock.call.is_block_device(dev),
mock.call.populate_image(image_path, dev),
mock.call.logout_iscsi(address, port, iqn),
mock.call.delete_iscsi(address, port, iqn)]
utils.deploy_disk_image(address, port, iqn, lun,
image_path, node_uuid)
self.assertEqual(calls_expected, parent_mock.mock_calls)
@mock.patch.object(common_utils, 'execute')
def test_verify_iscsi_connection_raises(self, mock_exec):
iqn = 'iqn.xyz'
@ -552,6 +679,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock_check_dev.assert_called_once_with(address, port, iqn)
@mock.patch.object(utils, 'is_block_device', lambda d: True)
def test_always_logout_and_delete_iscsi(self):
"""Check if logout_iscsi() and delete_iscsi() are called.
@ -590,9 +718,9 @@ class PhysicalWorkTestCase(tests_base.TestCase):
parent_mock.get_image_mb.return_value = 1
parent_mock.work_on_disk.side_effect = TestException
calls_expected = [mock.call.get_dev(address, port, iqn, lun),
mock.call.get_image_mb(image_path),
mock.call.discovery(address, port),
mock.call.login_iscsi(address, port, iqn),
mock.call.get_image_mb(image_path),
mock.call.work_on_disk(dev, root_mb, swap_mb,
ephemeral_mb,
ephemeral_format, image_path,
@ -602,7 +730,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock.call.logout_iscsi(address, port, iqn),
mock.call.delete_iscsi(address, port, iqn)]
self.assertRaises(TestException, utils.deploy,
self.assertRaises(TestException, utils.deploy_partition_image,
address, port, iqn, lun, image_path,
root_mb, swap_mb, ephemeral_mb, ephemeral_format,
node_uuid)
@ -623,36 +751,73 @@ class SwitchPxeConfigTestCase(tests_base.TestCase):
self.addCleanup(os.unlink, fname)
return fname
def test_switch_pxe_config(self):
def test_switch_pxe_config_partition_image(self):
boot_mode = 'bios'
fname = self._create_config()
utils.switch_pxe_config(fname,
'12345678-1234-1234-1234-1234567890abcdef',
boot_mode)
'12345678-1234-1234-1234-1234567890abcdef',
boot_mode,
False)
with open(fname, 'r') as f:
pxeconf = f.read()
self.assertEqual(_PXECONF_BOOT, pxeconf)
self.assertEqual(_PXECONF_BOOT_PARTITION, pxeconf)
def test_switch_ipxe_config(self):
def test_switch_pxe_config_whole_disk_image(self):
boot_mode = 'bios'
fname = self._create_config()
utils.switch_pxe_config(fname,
'0x12345678',
boot_mode,
True)
with open(fname, 'r') as f:
pxeconf = f.read()
self.assertEqual(_PXECONF_BOOT_WHOLE_DISK, pxeconf)
def test_switch_ipxe_config_partition_image(self):
boot_mode = 'bios'
cfg.CONF.set_override('ipxe_enabled', True, 'pxe')
fname = self._create_config(ipxe=True)
utils.switch_pxe_config(fname,
'12345678-1234-1234-1234-1234567890abcdef',
boot_mode)
'12345678-1234-1234-1234-1234567890abcdef',
boot_mode,
False)
with open(fname, 'r') as f:
pxeconf = f.read()
self.assertEqual(_IPXECONF_BOOT, pxeconf)
self.assertEqual(_IPXECONF_BOOT_PARTITION, pxeconf)
def test_switch_uefi_pxe_config(self):
def test_switch_ipxe_config_whole_disk_image(self):
boot_mode = 'bios'
cfg.CONF.set_override('ipxe_enabled', True, 'pxe')
fname = self._create_config(ipxe=True)
utils.switch_pxe_config(fname,
'0x12345678',
boot_mode,
True)
with open(fname, 'r') as f:
pxeconf = f.read()
self.assertEqual(_IPXECONF_BOOT_WHOLE_DISK, pxeconf)
def test_switch_uefi_pxe_config_partition_image(self):
boot_mode = 'uefi'
fname = self._create_config(boot_mode=boot_mode)
utils.switch_pxe_config(fname,
'12345678-1234-1234-1234-1234567890abcdef',
boot_mode)
'12345678-1234-1234-1234-1234567890abcdef',
boot_mode,
False)
with open(fname, 'r') as f:
pxeconf = f.read()
self.assertEqual(_UEFI_PXECONF_BOOT, pxeconf)
self.assertEqual(_UEFI_PXECONF_BOOT_PARTITION, pxeconf)
def test_switch_uefi_config_whole_disk_image(self):
boot_mode = 'uefi'
fname = self._create_config(boot_mode=boot_mode)
utils.switch_pxe_config(fname,
'0x12345678',
boot_mode,
True)
with open(fname, 'r') as f:
pxeconf = f.read()
self.assertEqual(_UEFI_PXECONF_BOOT_WHOLE_DISK, pxeconf)
@mock.patch('time.sleep', lambda sec: None)
@ -727,34 +892,21 @@ class WorkOnDiskTestCase(tests_base.TestCase):
self.mock_mp.return_value = {'swap': self.swap_part,
'root': self.root_part}
def test_no_parent_device(self):
def test_no_root_partition(self):
self.mock_ibd.return_value = False
self.assertRaises(exception.InstanceDeployFailure,
utils.work_on_disk, self.dev, self.root_mb,
self.swap_mb, self.ephemeral_mb,
self.ephemeral_format, self.image_path, 'fake-uuid')
self.mock_ibd.assert_called_once_with(self.dev)
self.assertFalse(self.mock_mp.called,
"make_partitions mock was unexpectedly called.")
def test_no_root_partition(self):
self.mock_ibd.side_effect = [True, False]
calls = [mock.call(self.dev),
mock.call(self.root_part)]
self.assertRaises(exception.InstanceDeployFailure,
utils.work_on_disk, self.dev, self.root_mb,
self.swap_mb, self.ephemeral_mb,
self.ephemeral_format, self.image_path, 'fake-uuid')
self.assertEqual(self.mock_ibd.call_args_list, calls)
self.mock_ibd.assert_called_once_with(self.root_part)
self.mock_mp.assert_called_once_with(self.dev, self.root_mb,
self.swap_mb, self.ephemeral_mb,
self.configdrive_mb, commit=True,
boot_option="netboot")
def test_no_swap_partition(self):
self.mock_ibd.side_effect = [True, True, False]
calls = [mock.call(self.dev),
mock.call(self.root_part),
self.mock_ibd.side_effect = [True, False]
calls = [mock.call(self.root_part),
mock.call(self.swap_part)]
self.assertRaises(exception.InstanceDeployFailure,
utils.work_on_disk, self.dev, self.root_mb,
@ -776,9 +928,8 @@ class WorkOnDiskTestCase(tests_base.TestCase):
self.mock_mp.return_value = {'ephemeral': ephemeral_part,
'swap': swap_part,
'root': root_part}
self.mock_ibd.side_effect = [True, True, True, False]
calls = [mock.call(self.dev),
mock.call(root_part),
self.mock_ibd.side_effect = [True, True, False]
calls = [mock.call(root_part),
mock.call(swap_part),
mock.call(ephemeral_part)]
self.assertRaises(exception.InstanceDeployFailure,
@ -804,9 +955,8 @@ class WorkOnDiskTestCase(tests_base.TestCase):
self.mock_mp.return_value = {'swap': swap_part,
'configdrive': configdrive_part,
'root': root_part}
self.mock_ibd.side_effect = [True, True, True, False]
calls = [mock.call(self.dev),
mock.call(root_part),
self.mock_ibd.side_effect = [True, True, False]
calls = [mock.call(root_part),
mock.call(swap_part),
mock.call(configdrive_part)]
self.assertRaises(exception.InstanceDeployFailure,
@ -1217,3 +1367,41 @@ class TrySetBootDeviceTestCase(db_base.DbTestCase):
task, boot_devices.DISK, persistent=True)
node_set_boot_device_mock.assert_called_once_with(
task, boot_devices.DISK, persistent=True)
@mock.patch.object(utils, 'is_block_device')
@mock.patch.object(utils, 'login_iscsi', lambda *_: None)
@mock.patch.object(utils, 'discovery', lambda *_: None)
@mock.patch.object(utils, 'logout_iscsi', lambda *_: None)
@mock.patch.object(utils, 'delete_iscsi', lambda *_: None)
@mock.patch.object(utils, 'get_dev', lambda *_: '/dev/fake')
class ISCSISetupAndHandleErrorsTestCase(tests_base.TestCase):
def test_no_parent_device(self, mock_ibd):
address = '127.0.0.1'
port = 3306
iqn = 'iqn.xyz'
lun = 1
image_path = '/tmp/xyz/image'
mock_ibd.return_value = False
expected_dev = '/dev/fake'
with testtools.ExpectedException(exception.InstanceDeployFailure):
with utils._iscsi_setup_and_handle_errors(
address, port, iqn, lun, image_path) as dev:
self.assertEqual(expected_dev, dev)
mock_ibd.assert_called_once_with(expected_dev)
def test_parent_device_yield(self, mock_ibd):
address = '127.0.0.1'
port = 3306
iqn = 'iqn.xyz'
lun = 1
image_path = '/tmp/xyz/image'
expected_dev = '/dev/fake'
mock_ibd.return_value = True
with utils._iscsi_setup_and_handle_errors(address, port,
iqn, lun, image_path) as dev:
self.assertEqual(expected_dev, dev)
mock_ibd.assert_called_once_with(expected_dev)

View File

@ -42,15 +42,18 @@ CONF = cfg.CONF
INST_INFO_DICT = db_utils.get_test_pxe_instance_info()
DRV_INFO_DICT = db_utils.get_test_pxe_driver_info()
DRV_INTERNAL_INFO_DICT = db_utils.get_test_pxe_driver_internal_info()
class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
def test_parse_instance_info_good(self):
# make sure we get back the expected things
node = obj_utils.create_test_node(self.context,
driver='fake_pxe',
instance_info=INST_INFO_DICT)
node = obj_utils.create_test_node(
self.context, driver='fake_pxe',
instance_info=INST_INFO_DICT,
driver_internal_info=DRV_INTERNAL_INFO_DICT
)
info = iscsi_deploy.parse_instance_info(node)
self.assertIsNotNone(info.get('image_source'))
self.assertIsNotNone(info.get('root_gb'))
@ -61,7 +64,10 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
# make sure error is raised when info is missing
info = dict(INST_INFO_DICT)
del info['image_source']
node = obj_utils.create_test_node(self.context, instance_info=info)
node = obj_utils.create_test_node(
self.context, instance_info=info,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
self.assertRaises(exception.MissingParameterValue,
iscsi_deploy.parse_instance_info,
node)
@ -70,7 +76,11 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
# make sure error is raised when info is missing
info = dict(INST_INFO_DICT)
del info['root_gb']
node = obj_utils.create_test_node(self.context, instance_info=info)
node = obj_utils.create_test_node(
self.context, instance_info=info,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
self.assertRaises(exception.MissingParameterValue,
iscsi_deploy.parse_instance_info,
node)
@ -78,7 +88,10 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
def test_parse_instance_info_invalid_root_gb(self):
info = dict(INST_INFO_DICT)
info['root_gb'] = 'foobar'
node = obj_utils.create_test_node(self.context, instance_info=info)
node = obj_utils.create_test_node(
self.context, instance_info=info,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
self.assertRaises(exception.InvalidParameterValue,
iscsi_deploy.parse_instance_info,
node)
@ -89,7 +102,10 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
info = dict(INST_INFO_DICT)
info['ephemeral_gb'] = ephemeral_gb
info['ephemeral_format'] = ephemeral_fmt
node = obj_utils.create_test_node(self.context, instance_info=info)
node = obj_utils.create_test_node(
self.context, instance_info=info,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
data = iscsi_deploy.parse_instance_info(node)
self.assertEqual(ephemeral_gb, data.get('ephemeral_gb'))
self.assertEqual(ephemeral_fmt, data.get('ephemeral_format'))
@ -98,7 +114,11 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
info = dict(INST_INFO_DICT)
info['ephemeral_gb'] = 'foobar'
info['ephemeral_format'] = 'exttest'
node = obj_utils.create_test_node(self.context, instance_info=info)
node = obj_utils.create_test_node(
self.context, instance_info=info,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
self.assertRaises(exception.InvalidParameterValue,
iscsi_deploy.parse_instance_info,
node)
@ -110,7 +130,10 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
info['ephemeral_gb'] = ephemeral_gb
info['ephemeral_format'] = None
self.config(default_ephemeral_format=ephemeral_fmt, group='pxe')
node = obj_utils.create_test_node(self.context, instance_info=info)
node = obj_utils.create_test_node(
self.context, instance_info=info,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
instance_info = iscsi_deploy.parse_instance_info(node)
self.assertEqual(ephemeral_fmt, instance_info['ephemeral_format'])
@ -119,9 +142,12 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
for opt in ['true', 'TRUE', 'True', 't',
'on', 'yes', 'y', '1']:
info['preserve_ephemeral'] = opt
node = obj_utils.create_test_node(self.context,
uuid=uuidutils.generate_uuid(),
instance_info=info)
node = obj_utils.create_test_node(
self.context, uuid=uuidutils.generate_uuid(),
instance_info=info,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
data = iscsi_deploy.parse_instance_info(node)
self.assertTrue(data.get('preserve_ephemeral'))
@ -130,16 +156,21 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
for opt in ['false', 'FALSE', 'False', 'f',
'off', 'no', 'n', '0']:
info['preserve_ephemeral'] = opt
node = obj_utils.create_test_node(self.context,
uuid=uuidutils.generate_uuid(),
instance_info=info)
node = obj_utils.create_test_node(
self.context, uuid=uuidutils.generate_uuid(),
instance_info=info,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
data = iscsi_deploy.parse_instance_info(node)
self.assertFalse(data.get('preserve_ephemeral'))
def test_parse_instance_info_invalid_preserve_ephemeral(self):
info = dict(INST_INFO_DICT)
info['preserve_ephemeral'] = 'foobar'
node = obj_utils.create_test_node(self.context, instance_info=info)
node = obj_utils.create_test_node(
self.context, instance_info=info,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
self.assertRaises(exception.InvalidParameterValue,
iscsi_deploy.parse_instance_info,
node)
@ -147,7 +178,10 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
def test_parse_instance_info_configdrive(self):
info = dict(INST_INFO_DICT)
info['configdrive'] = 'http://1.2.3.4/cd'
node = obj_utils.create_test_node(self.context, instance_info=info)
node = obj_utils.create_test_node(
self.context, instance_info=info,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
instance_info = iscsi_deploy.parse_instance_info(node)
self.assertEqual('http://1.2.3.4/cd', instance_info['configdrive'])
@ -156,23 +190,31 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
info['image_source'] = 'file:///image.qcow2'
info['kernel'] = 'file:///image.vmlinuz'
info['ramdisk'] = 'file:///image.initrd'
node = obj_utils.create_test_node(self.context, instance_info=info)
node = obj_utils.create_test_node(
self.context, instance_info=info,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
iscsi_deploy.parse_instance_info(node)
def test_parse_instance_info_nonglance_image_no_kernel(self):
info = INST_INFO_DICT.copy()
info['image_source'] = 'file:///image.qcow2'
info['ramdisk'] = 'file:///image.initrd'
node = obj_utils.create_test_node(self.context, instance_info=info)
node = obj_utils.create_test_node(
self.context, instance_info=info,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
self.assertRaises(exception.MissingParameterValue,
iscsi_deploy.parse_instance_info, node)
@mock.patch.object(image_service, 'get_image_service')
def test_validate_image_properties_glance_image(self, image_service_mock):
node = obj_utils.create_test_node(self.context,
driver='fake_pxe',
instance_info=INST_INFO_DICT,
driver_info=DRV_INFO_DICT)
node = obj_utils.create_test_node(
self.context, driver='fake_pxe',
instance_info=INST_INFO_DICT,
driver_info=DRV_INFO_DICT,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
d_info = pxe._parse_deploy_info(node)
image_service_mock.return_value.show.return_value = {
'properties': {'kernel_id': '1111', 'ramdisk_id': '2222'},
@ -187,10 +229,12 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
@mock.patch.object(image_service, 'get_image_service')
def test_validate_image_properties_glance_image_missing_prop(self,
image_service_mock):
node = obj_utils.create_test_node(self.context,
driver='fake_pxe',
instance_info=INST_INFO_DICT,
driver_info=DRV_INFO_DICT)
node = obj_utils.create_test_node(
self.context, driver='fake_pxe',
instance_info=INST_INFO_DICT,
driver_info=DRV_INFO_DICT,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
d_info = pxe._parse_deploy_info(node)
image_service_mock.return_value.show.return_value = {
'properties': {'kernel_id': '1111'},
@ -239,10 +283,12 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
'root_gb': 100,
}
image_service_show_mock.return_value = {'size': 1, 'properties': {}}
node = obj_utils.create_test_node(self.context,
driver='fake_pxe',
instance_info=instance_info,
driver_info=DRV_INFO_DICT)
node = obj_utils.create_test_node(
self.context, driver='fake_pxe',
instance_info=instance_info,
driver_info=DRV_INFO_DICT,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
d_info = pxe._parse_deploy_info(node)
iscsi_deploy.validate_image_properties(self.context, d_info,
['kernel', 'ramdisk'])
@ -260,15 +306,38 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
}
img_service_show_mock.side_effect = exception.ImageRefValidationFailed(
image_href='http://ubuntu', reason='HTTPError')
node = obj_utils.create_test_node(self.context,
driver='fake_pxe',
instance_info=instance_info,
driver_info=DRV_INFO_DICT)
node = obj_utils.create_test_node(
self.context, driver='fake_pxe',
instance_info=instance_info,
driver_info=DRV_INFO_DICT,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
d_info = pxe._parse_deploy_info(node)
self.assertRaises(exception.InvalidParameterValue,
iscsi_deploy.validate_image_properties, self.context,
d_info, ['kernel', 'ramdisk'])
def test_parse_instance_info_whole_disk_image(self):
driver_internal_info = dict(DRV_INTERNAL_INFO_DICT)
driver_internal_info['is_whole_disk_image'] = True
node = obj_utils.create_test_node(
self.context, instance_info=INST_INFO_DICT,
driver_internal_info=driver_internal_info,
)
instance_info = iscsi_deploy.parse_instance_info(node)
self.assertIsNotNone(instance_info.get('image_source'))
self.assertIsNotNone(instance_info.get('root_gb'))
self.assertEqual(0, instance_info.get('swap_mb'))
self.assertEqual(0, instance_info.get('ephemeral_gb'))
self.assertIsNone(instance_info.get('configdrive'))
def test_parse_instance_info_whole_disk_image_missing_root(self):
info = dict(INST_INFO_DICT)
del info['root_gb']
node = obj_utils.create_test_node(self.context, instance_info=info)
self.assertRaises(exception.InvalidParameterValue,
iscsi_deploy.parse_instance_info, node)
class IscsiDeployPrivateMethodsTestCase(db_base.DbTestCase):
@ -278,6 +347,7 @@ class IscsiDeployPrivateMethodsTestCase(db_base.DbTestCase):
'driver': 'fake_pxe',
'instance_info': INST_INFO_DICT,
'driver_info': DRV_INFO_DICT,
'driver_internal_info': DRV_INTERNAL_INFO_DICT,
}
mgr_utils.mock_the_extension_manager(driver="fake_pxe")
self.node = obj_utils.create_test_node(self.context, **n)
@ -304,6 +374,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
'driver': 'fake_pxe',
'instance_info': instance_info,
'driver_info': DRV_INFO_DICT,
'driver_internal_info': DRV_INTERNAL_INFO_DICT,
}
mgr_utils.mock_the_extension_manager(driver="fake_pxe")
self.node = obj_utils.create_test_node(self.context, **n)
@ -440,7 +511,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
@mock.patch.object(iscsi_deploy, 'InstanceImageCache')
@mock.patch.object(manager_utils, 'node_power_action')
@mock.patch.object(deploy_utils, 'deploy')
@mock.patch.object(deploy_utils, 'deploy_partition_image')
def test_continue_deploy_fail(self, deploy_mock, power_mock,
mock_image_cache):
kwargs = {'address': '123456', 'iqn': 'aaa-bbb', 'key': 'fake-56789'}
@ -464,7 +535,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
@mock.patch.object(iscsi_deploy, 'InstanceImageCache')
@mock.patch.object(manager_utils, 'node_power_action')
@mock.patch.object(deploy_utils, 'deploy')
@mock.patch.object(deploy_utils, 'deploy_partition_image')
def test_continue_deploy_ramdisk_fails(self, deploy_mock, power_mock,
mock_image_cache):
kwargs = {'address': '123456', 'iqn': 'aaa-bbb', 'key': 'fake-56789',
@ -488,7 +559,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
@mock.patch.object(iscsi_deploy, 'get_deploy_info')
@mock.patch.object(iscsi_deploy, 'InstanceImageCache')
@mock.patch.object(manager_utils, 'node_power_action')
@mock.patch.object(deploy_utils, 'deploy')
@mock.patch.object(deploy_utils, 'deploy_partition_image')
def test_continue_deploy(self, deploy_mock, power_mock, mock_image_cache,
mock_deploy_info, mock_log):
kwargs = {'address': '123456', 'iqn': 'aaa-bbb', 'key': 'fake-56789'}
@ -531,6 +602,47 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
mock_image_cache.assert_called_once_with()
mock_image_cache.return_value.clean_up.assert_called_once_with()
@mock.patch.object(iscsi_deploy, 'LOG')
@mock.patch.object(iscsi_deploy, 'get_deploy_info')
@mock.patch.object(iscsi_deploy, 'InstanceImageCache')
@mock.patch.object(manager_utils, 'node_power_action')
@mock.patch.object(deploy_utils, 'deploy_disk_image')
def test_continue_deploy_whole_disk_image(
self, deploy_mock, power_mock, mock_image_cache, mock_deploy_info,
mock_log):
kwargs = {'address': '123456', 'iqn': 'aaa-bbb', 'key': 'fake-56789'}
self.node.provision_state = states.DEPLOYWAIT
self.node.target_provision_state = states.ACTIVE
self.node.save()
mock_deploy_info.return_value = {
'address': '123456',
'image_path': (u'/var/lib/ironic/images/1be26c0b-03f2-4d2e-ae87-'
u'c02d7f33c123/disk'),
'iqn': 'aaa-bbb',
'lun': '1',
'node_uuid': u'1be26c0b-03f2-4d2e-ae87-c02d7f33c123',
'port': '3260',
'root_mb': 102400,
}
log_params = mock_deploy_info.return_value.copy()
expected_dict = {
'node': self.node.uuid,
'params': log_params,
}
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.node.driver_internal_info['is_whole_disk_image'] = True
mock_log.isEnabledFor.return_value = True
iscsi_deploy.continue_deploy(task, **kwargs)
mock_log.debug.assert_called_once_with(
mock.ANY, expected_dict)
self.assertEqual(states.DEPLOYWAIT, task.node.provision_state)
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
self.assertIsNone(task.node.last_error)
mock_image_cache.assert_called_once_with()
mock_image_cache.return_value.clean_up.assert_called_once_with()
def test_get_deploy_info_boot_option_default(self):
instance_info = self.node.instance_info
instance_info['deploy_key'] = 'key'
@ -588,8 +700,9 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
task, error=None, iqn='iqn-qweqwe', key='abcdef',
address='1.2.3.4')
self.assertEqual('some-root-uuid', ret_val)
self.assertEqual('some-root-uuid',
task.node.driver_internal_info['root_uuid'])
self.assertEqual(
'some-root-uuid',
task.node.driver_internal_info['root_uuid_or_disk_id'])
@mock.patch.object(iscsi_deploy, 'build_deploy_ramdisk_options')
def test_do_agent_iscsi_deploy_start_iscsi_failure(self,

View File

@ -50,16 +50,21 @@ CONF = cfg.CONF
INST_INFO_DICT = db_utils.get_test_pxe_instance_info()
DRV_INFO_DICT = db_utils.get_test_pxe_driver_info()
DRV_INTERNAL_INFO_DICT = db_utils.get_test_pxe_driver_internal_info()
class PXEValidateParametersTestCase(db_base.DbTestCase):
def test__parse_deploy_info(self):
# make sure we get back the expected things
node = obj_utils.create_test_node(self.context,
driver='fake_pxe',
instance_info=INST_INFO_DICT,
driver_info=DRV_INFO_DICT)
node = obj_utils.create_test_node(
self.context,
driver='fake_pxe',
instance_info=INST_INFO_DICT,
driver_info=DRV_INFO_DICT,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
info = pxe._parse_deploy_info(node)
self.assertIsNotNone(info.get('deploy_ramdisk'))
self.assertIsNotNone(info.get('deploy_kernel'))
@ -114,6 +119,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
'driver': 'fake_pxe',
'instance_info': INST_INFO_DICT,
'driver_info': DRV_INFO_DICT,
'driver_internal_info': DRV_INTERNAL_INFO_DICT,
}
mgr_utils.mock_the_extension_manager(driver="fake_pxe")
self.node = obj_utils.create_test_node(self.context, **n)
@ -159,6 +165,25 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
self.assertEqual('instance_ramdisk_uuid',
self.node.instance_info.get('ramdisk'))
@mock.patch.object(base_image_service.BaseImageService, '_show')
def test__get_image_info_whole_disk_image(self, show_mock):
properties = {'properties': None}
expected_info = {'deploy_ramdisk':
(DRV_INFO_DICT['deploy_ramdisk'],
os.path.join(CONF.pxe.tftp_root,
self.node.uuid,
'deploy_ramdisk')),
'deploy_kernel':
(DRV_INFO_DICT['deploy_kernel'],
os.path.join(CONF.pxe.tftp_root,
self.node.uuid,
'deploy_kernel'))}
show_mock.return_value = properties
self.node.driver_internal_info['is_whole_disk_image'] = True
image_info = pxe._get_image_info(self.node, self.context)
self.assertEqual(expected_info, image_info)
@mock.patch.object(iscsi_deploy, 'build_deploy_ramdisk_options')
@mock.patch.object(pxe_utils, '_build_pxe_config')
def _test_build_pxe_config_options(self, build_pxe_mock, deploy_opts_mock,
@ -231,9 +256,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
'ramdisk': ('ramdisk_id',
os.path.join(root_dir,
self.node.uuid,
'ramdisk'))
}
'ramdisk'))}
options = pxe._build_pxe_config_options(self.node,
image_info,
self.context)
@ -245,6 +268,66 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
def test__build_pxe_config_options_ipxe(self):
self._test_build_pxe_config_options(ipxe_enabled=True)
@mock.patch.object(iscsi_deploy, 'build_deploy_ramdisk_options')
@mock.patch.object(pxe_utils, '_build_pxe_config')
def _test_build_pxe_config_options_whole_disk_image(self, build_pxe_mock,
deploy_opts_mock, ipxe_enabled=False):
self.config(pxe_append_params='test_param', group='pxe')
# NOTE: right '/' should be removed from url string
self.config(api_url='http://192.168.122.184:6385/', group='conductor')
self.config(disk_devices='sda', group='pxe')
fake_deploy_opts = {'iscsi_target_iqn': 'fake-iqn',
'deployment_id': 'fake-deploy-id',
'deployment_key': 'fake-deploy-key',
'disk': 'fake-disk',
'ironic_api_url': 'fake-api-url'}
deploy_opts_mock.return_value = fake_deploy_opts
tftp_server = CONF.pxe.tftp_server
if ipxe_enabled:
http_url = 'http://192.1.2.3:1234'
self.config(ipxe_enabled=True, group='pxe')
self.config(http_url=http_url, group='pxe')
deploy_kernel = os.path.join(http_url, self.node.uuid,
'deploy_kernel')
deploy_ramdisk = os.path.join(http_url, self.node.uuid,
'deploy_ramdisk')
root_dir = CONF.pxe.http_root
else:
deploy_kernel = os.path.join(CONF.pxe.tftp_root, self.node.uuid,
'deploy_kernel')
deploy_ramdisk = os.path.join(CONF.pxe.tftp_root, self.node.uuid,
'deploy_ramdisk')
root_dir = CONF.pxe.tftp_root
expected_options = {
'deployment_ari_path': deploy_ramdisk,
'pxe_append_params': 'test_param',
'deployment_aki_path': deploy_kernel,
'tftp_server': tftp_server,
}
expected_options.update(fake_deploy_opts)
image_info = {'deploy_kernel': ('deploy_kernel',
os.path.join(root_dir,
self.node.uuid,
'deploy_kernel')),
'deploy_ramdisk': ('deploy_ramdisk',
os.path.join(root_dir,
self.node.uuid,
'deploy_ramdisk')),
}
self.node.driver_internal_info['is_whole_disk_image'] = True
options = pxe._build_pxe_config_options(self.node,
image_info,
self.context)
self.assertEqual(expected_options, options)
def test_get_token_file_path(self):
node_uuid = self.node.uuid
self.assertEqual('/tftpboot/token-' + node_uuid,
@ -311,10 +394,12 @@ class PXEDriverTestCase(db_base.DbTestCase):
mgr_utils.mock_the_extension_manager(driver="fake_pxe")
instance_info = INST_INFO_DICT
instance_info['deploy_key'] = 'fake-56789'
self.node = obj_utils.create_test_node(self.context,
driver='fake_pxe',
instance_info=instance_info,
driver_info=DRV_INFO_DICT)
self.node = obj_utils.create_test_node(
self.context,
driver='fake_pxe',
instance_info=instance_info,
driver_info=DRV_INFO_DICT,
driver_internal_info=DRV_INTERNAL_INFO_DICT)
self.port = obj_utils.create_test_port(self.context,
node_id=self.node.id)
self.config(group='conductor', api_url='http://127.0.0.1:1234/')
@ -338,6 +423,13 @@ class PXEDriverTestCase(db_base.DbTestCase):
shared=True) as task:
task.driver.deploy.validate(task)
@mock.patch.object(base_image_service.BaseImageService, '_show')
def test_validate_good_whole_disk_image(self, mock_glance):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
task.node.driver_internal_info['is_whole_disk_image'] = True
task.driver.deploy.validate(task)
def test_validate_fail(self):
info = dict(INST_INFO_DICT)
del info['image_source']
@ -570,7 +662,8 @@ class PXEDriverTestCase(db_base.DbTestCase):
mock_get_cap.return_value = None
self.node.provision_state = states.ACTIVE
self.node.driver_internal_info = {'root_uuid': 'abcd'}
self.node.driver_internal_info = {'root_uuid_or_disk_id': 'abcd',
'is_whole_disk_image': False}
self.node.save()
with task_manager.acquire(self.context, self.node.uuid) as task:
@ -583,7 +676,7 @@ class PXEDriverTestCase(db_base.DbTestCase):
task.node, None)
mock_pxe_get_cfg.assert_called_once_with(task.node.uuid)
mock_switch.assert_called_once_with('/path', 'abcd', None)
mock_switch.assert_called_once_with('/path', 'abcd', None, False)
@mock.patch.object(keystone, 'token_expires_soon')
@mock.patch.object(deploy_utils, 'get_image_mb')
@ -712,7 +805,7 @@ class PXEDriverTestCase(db_base.DbTestCase):
@mock.patch.object(deploy_utils, 'notify_deploy_complete')
@mock.patch.object(deploy_utils, 'switch_pxe_config')
@mock.patch.object(iscsi_deploy, 'InstanceImageCache')
@mock.patch.object(deploy_utils, 'deploy')
@mock.patch.object(deploy_utils, 'deploy_partition_image')
def _test_continue_deploy(self, is_localboot, mock_deploy,
mock_image_cache, mock_switch_config,
notify_mock, mock_node_boot_dev, mock_clean_pxe):
@ -732,6 +825,7 @@ class PXEDriverTestCase(db_base.DbTestCase):
root_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
mock_deploy.return_value = root_uuid
boot_mode = None
is_whole_disk_image = False
with task_manager.acquire(self.context, self.node.uuid) as task:
task.driver.vendor._continue_deploy(
@ -741,7 +835,7 @@ class PXEDriverTestCase(db_base.DbTestCase):
self.assertEqual(states.ACTIVE, self.node.provision_state)
self.assertEqual(states.NOSTATE, self.node.target_provision_state)
self.assertEqual(states.POWER_ON, self.node.power_state)
self.assertIn('root_uuid', self.node.driver_internal_info)
self.assertIn('root_uuid_or_disk_id', self.node.driver_internal_info)
self.assertIsNone(self.node.last_error)
self.assertFalse(os.path.exists(token_path))
mock_image_cache.assert_called_once_with()
@ -754,8 +848,70 @@ class PXEDriverTestCase(db_base.DbTestCase):
mock_clean_pxe.assert_called_once_with(mock.ANY)
self.assertFalse(mock_switch_config.called)
else:
mock_switch_config.assert_called_once_with(
pxe_config_path, root_uuid, boot_mode)
mock_switch_config.assert_called_once_with(pxe_config_path,
root_uuid,
boot_mode,
is_whole_disk_image)
self.assertFalse(mock_node_boot_dev.called)
self.assertFalse(mock_clean_pxe.called)
@mock.patch.object(pxe_utils, 'clean_up_pxe_config')
@mock.patch.object(manager_utils, 'node_set_boot_device')
@mock.patch.object(deploy_utils, 'notify_deploy_complete')
@mock.patch.object(deploy_utils, 'switch_pxe_config')
@mock.patch.object(iscsi_deploy, 'InstanceImageCache')
@mock.patch.object(deploy_utils, 'deploy_disk_image')
def _test_continue_deploy_whole_disk_image(self, is_localboot,
mock_deploy,
mock_image_cache,
mock_switch_config,
notify_mock,
mock_node_boot_dev,
mock_clean_pxe):
token_path = self._create_token_file()
# set local boot
if is_localboot:
i_info = self.node.instance_info
i_info['capabilities'] = '{"boot_option": "local"}'
self.node.instance_info = i_info
self.node.power_state = states.POWER_ON
self.node.provision_state = states.DEPLOYWAIT
self.node.target_provision_state = states.ACTIVE
self.node.save()
boot_mode = None
is_whole_disk_image = True
disk_id = '0x12345678'
mock_deploy.return_value = disk_id
with task_manager.acquire(self.context, self.node.uuid) as task:
task.node.driver_internal_info['is_whole_disk_image'] = True
task.driver.vendor._continue_deploy(task, address='123456',
iqn='aaa-bbb',
key='fake-56789')
self.node.refresh()
self.assertEqual(states.ACTIVE, self.node.provision_state)
self.assertEqual(states.NOSTATE, self.node.target_provision_state)
self.assertEqual(states.POWER_ON, self.node.power_state)
self.assertIsNone(self.node.last_error)
self.assertFalse(os.path.exists(token_path))
mock_image_cache.assert_called_once_with()
mock_image_cache.return_value.clean_up.assert_called_once_with()
pxe_config_path = pxe_utils.get_pxe_config_file_path(self.node.uuid)
notify_mock.assert_called_once_with('123456')
if is_localboot:
mock_node_boot_dev.assert_called_once_with(
mock.ANY, boot_devices.DISK, persistent=True)
mock_clean_pxe.assert_called_once_with(mock.ANY)
self.assertFalse(mock_switch_config.called)
else:
mock_switch_config.assert_called_once_with(pxe_config_path,
disk_id,
boot_mode,
is_whole_disk_image)
self.assertFalse(mock_node_boot_dev.called)
self.assertFalse(mock_clean_pxe.called)
@ -765,6 +921,12 @@ class PXEDriverTestCase(db_base.DbTestCase):
def test_continue_deploy_localboot(self):
self._test_continue_deploy(True)
def test_continue_deploy_whole_disk_image(self):
self._test_continue_deploy_whole_disk_image(False)
def test_continue_deploy_whole_disk_image_localboot(self):
self._test_continue_deploy_whole_disk_image(True)
def test_continue_deploy_invalid(self):
self.node.power_state = states.POWER_ON
self.node.provision_state = states.AVAILABLE
@ -821,10 +983,12 @@ class CleanUpTestCase(db_base.DbTestCase):
mgr_utils.mock_the_extension_manager(driver="fake_pxe")
instance_info = INST_INFO_DICT
instance_info['deploy_key'] = 'fake-56789'
self.node = obj_utils.create_test_node(self.context,
driver='fake_pxe',
instance_info=instance_info,
driver_info=DRV_INFO_DICT)
self.node = obj_utils.create_test_node(
self.context, driver='fake_pxe',
instance_info=instance_info,
driver_info=DRV_INFO_DICT,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
def test_clean_up(self, mock_image_info, mock_cache, mock_pxe_clean,
mock_iscsi_clean, mock_unlink):
@ -866,10 +1030,12 @@ class CleanUpFullFlowTestCase(db_base.DbTestCase):
mgr_utils.mock_the_extension_manager(driver="fake_pxe")
instance_info = INST_INFO_DICT
instance_info['deploy_key'] = 'fake-56789'
self.node = obj_utils.create_test_node(self.context,
driver='fake_pxe',
instance_info=instance_info,
driver_info=DRV_INFO_DICT)
self.node = obj_utils.create_test_node(
self.context, driver='fake_pxe',
instance_info=instance_info,
driver_info=DRV_INFO_DICT,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
self.port = obj_utils.create_test_port(self.context,
node_id=self.node.id)
@ -945,9 +1111,12 @@ class TestAgentVendorPassthru(db_base.DbTestCase):
mgr_utils.mock_the_extension_manager()
self.driver = driver_factory.get_driver("fake")
self.driver.vendor = pxe.VendorPassthru()
self.node = obj_utils.create_test_node(self.context, driver='fake',
instance_info=INST_INFO_DICT,
driver_info=DRV_INFO_DICT)
self.node = obj_utils.create_test_node(
self.context, driver='fake',
instance_info=INST_INFO_DICT,
driver_info=DRV_INFO_DICT,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
self.node.driver_internal_info['agent_url'] = 'http://1.2.3.4:1234'
self.task = mock.Mock(spec=task_manager.TaskManager)
self.task.shared = False
@ -966,7 +1135,6 @@ class TestAgentVendorPassthru(db_base.DbTestCase):
reboot_and_finish_deploy_mock):
do_agent_iscsi_deploy_mock.return_value = 'some-root-uuid'
self.driver.vendor.continue_deploy(self.task)
destroy_token_file_mock.assert_called_once_with(self.node)
do_agent_iscsi_deploy_mock.assert_called_once_with(
@ -974,7 +1142,7 @@ class TestAgentVendorPassthru(db_base.DbTestCase):
tftp_config = '/tftpboot/%s/config' % self.node.uuid
switch_pxe_config_mock.assert_called_once_with(tftp_config,
'some-root-uuid',
None)
None, False)
reboot_and_finish_deploy_mock.assert_called_once_with(self.task)
@mock.patch.object(agent_base_vendor.BaseAgentVendor,

View File

@ -25,6 +25,7 @@ from oslo_config import cfg
import six.moves.builtins as __builtin__
from ironic.common import exception
from ironic.common.glance_service import service_utils as glance_utils
from ironic.common import image_service
from ironic.common import images
from ironic.common import utils
@ -214,6 +215,68 @@ class IronicImagesTestCase(base.TestCase):
qemu_img_info_mock.assert_called_once_with('path')
self.assertEqual(1, size)
@mock.patch.object(images, 'get_image_properties')
@mock.patch.object(glance_utils, 'is_glance_image')
def test_is_whole_disk_image_no_img_src(self, mock_igi, mock_gip):
instance_info = {'image_source': ''}
iwdi = images.is_whole_disk_image('context', instance_info)
self.assertIsNone(iwdi)
self.assertFalse(mock_igi.called)
self.assertFalse(mock_gip.called)
@mock.patch.object(images, 'get_image_properties')
@mock.patch.object(glance_utils, 'is_glance_image')
def test_is_whole_disk_image_partition_image(self, mock_igi, mock_gip):
mock_igi.return_value = True
mock_gip.return_value = {'kernel_id': 'kernel',
'ramdisk_id': 'ramdisk'}
instance_info = {'image_source': 'glance://partition_image'}
image_source = instance_info['image_source']
is_whole_disk_image = images.is_whole_disk_image('context',
instance_info)
self.assertFalse(is_whole_disk_image)
mock_igi.assert_called_once_with(image_source)
mock_gip.assert_called_once_with('context', image_source)
@mock.patch.object(images, 'get_image_properties')
@mock.patch.object(glance_utils, 'is_glance_image')
def test_is_whole_disk_image_whole_disk_image(self, mock_igi, mock_gip):
mock_igi.return_value = True
mock_gip.return_value = {}
instance_info = {'image_source': 'glance://whole_disk_image'}
image_source = instance_info['image_source']
is_whole_disk_image = images.is_whole_disk_image('context',
instance_info)
self.assertTrue(is_whole_disk_image)
mock_igi.assert_called_once_with(image_source)
mock_gip.assert_called_once_with('context', image_source)
@mock.patch.object(images, 'get_image_properties')
@mock.patch.object(glance_utils, 'is_glance_image')
def test_is_whole_disk_image_partition_non_glance(self, mock_igi,
mock_gip):
mock_igi.return_value = False
instance_info = {'image_source': 'partition_image',
'kernel': 'kernel',
'ramdisk': 'ramdisk'}
is_whole_disk_image = images.is_whole_disk_image('context',
instance_info)
self.assertFalse(is_whole_disk_image)
self.assertFalse(mock_gip.called)
mock_igi.assert_called_once_with(instance_info['image_source'])
@mock.patch.object(images, 'get_image_properties')
@mock.patch.object(glance_utils, 'is_glance_image')
def test_is_whole_disk_image_whole_disk_non_glance(self, mock_igi,
mock_gip):
mock_igi.return_value = False
instance_info = {'image_source': 'whole_disk_image'}
is_whole_disk_image = images.is_whole_disk_image('context',
instance_info)
self.assertTrue(is_whole_disk_image)
self.assertFalse(mock_gip.called)
mock_igi.assert_called_once_with(instance_info['image_source'])
class FsImageTestCase(base.TestCase):