Take iSCSI deploy out of pxe driver

This commit aims at separating the iSCSI deploy portions
from pxe driver so that it can reused in the newly proposed
iLO virtualmedia driver.

Implements: blueprint ironic-ilo-virtualmedia-driver
Change-Id: Id0a4b0fab797c884448a27b1cd5e3a1da05786a7
This commit is contained in:
Ramakrishnan G 2014-07-18 14:17:39 +05:30
parent e0e0291efd
commit 6d16dd8951
10 changed files with 855 additions and 577 deletions

View File

@ -1057,16 +1057,13 @@
[pxe]
#
# Options defined in ironic.drivers.modules.pxe
# Options defined in ironic.drivers.modules.iscsi_deploy
#
# Additional append parameters for baremetal PXE boot. (string
# value)
#pxe_append_params=nofb nomodeset vga=normal
# Template file for PXE configuration. (string value)
#pxe_config_template=$pybasedir/drivers/modules/pxe_config.template
# Default file system format for ephemeral partition, if one
# is created. (string value)
#default_ephemeral_format=ext4
@ -1074,6 +1071,30 @@
# Directory where images are stored on disk. (string value)
#images_path=/var/lib/ironic/images/
# Directory where master instance images are stored on disk.
# (string value)
#instance_master_path=/var/lib/ironic/master_images
# Maximum size (in MiB) of cache for master images, including
# those in use. (integer value)
#image_cache_size=20480
# Maximum TTL (in minutes) for old master images in cache.
# (integer value)
#image_cache_ttl=10080
# The disk devices to scan while doing the deploy. (string
# value)
#disk_devices=cciss/c0d0,sda,hda,vda
#
# Options defined in ironic.drivers.modules.pxe
#
# Template file for PXE configuration. (string value)
#pxe_config_template=$pybasedir/drivers/modules/pxe_config.template
# IP address of Ironic compute node's tftp server. (string
# value)
#tftp_server=$my_ip
@ -1085,21 +1106,9 @@
# (string value)
#tftp_master_path=/tftpboot/master_images
# Directory where master instance images are stored on disk.
# (string value)
#instance_master_path=/var/lib/ironic/master_images
# Neutron bootfile DHCP parameter. (string value)
#pxe_bootfile_name=pxelinux.0
# Maximum size (in MiB) of cache for master images, including
# those in use. (integer value)
#image_cache_size=20480
# Maximum TTL (in minutes) for old master images in cache.
# (integer value)
#image_cache_ttl=10080
# Ironic compute node's HTTP server URL. Example:
# http://192.1.2.3:8080 (string value)
#http_url=<None>

View File

@ -26,6 +26,7 @@ from oslo.utils import excutils
from ironic.common import disk_partitioner
from ironic.common import exception
from ironic.common import utils
from ironic.drivers.modules import image_cache
from ironic.openstack.common import log as logging
from ironic.openstack.common import processutils
@ -279,7 +280,7 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format,
:param preserve_ephemeral: If True, no filesystem is written to the
ephemeral block device, preserving whatever content it had (if the
partition table has not changed).
:returns: the UUID of the root partition.
"""
if not is_block_device(dev):
raise exception.InstanceDeployFailure(_("Parent device '%s' not found")
@ -324,7 +325,7 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format,
return root_uuid
def deploy(address, port, iqn, lun, image_path, pxe_config_path,
def deploy(address, port, iqn, lun, image_path,
root_mb, swap_mb, ephemeral_mb, ephemeral_format, node_uuid,
preserve_ephemeral=False):
"""All-in-one function to deploy a node.
@ -334,7 +335,6 @@ def deploy(address, port, iqn, lun, image_path, pxe_config_path,
:param iqn: The iSCSI qualified name.
:param lun: The iSCSI logical unit number.
:param image_path: Path for the instance's disk image.
:param pxe_config_path: Path for the instance PXE config file.
:param root_mb: Size of the root partition in megabytes.
:param swap_mb: Size of the swap partition in megabytes.
:param ephemeral_mb: Size of the ephemeral partition in megabytes. If 0,
@ -345,7 +345,7 @@ def deploy(address, port, iqn, lun, image_path, pxe_config_path,
:param preserve_ephemeral: If True, no filesystem is written to the
ephemeral block device, preserving whatever content it had (if the
partition table has not changed).
:returns: the UUID of the root partition.
"""
dev = get_dev(address, port, iqn, lun)
image_mb = get_image_mb(image_path)
@ -370,7 +370,59 @@ def deploy(address, port, iqn, lun, image_path, pxe_config_path,
finally:
logout_iscsi(address, port, iqn)
delete_iscsi(address, port, iqn)
switch_pxe_config(pxe_config_path, root_uuid)
return root_uuid
def notify_deploy_complete(address):
"""Notifies the completion of deployment to the baremetal node.
:param address: The IP address of the node.
"""
# Ensure the node started netcat on the port after POST the request.
time.sleep(3)
notify(address, 10000)
def check_for_missing_params(info_dict, error_msg, param_prefix=''):
"""Check for empty params in the provided dictionary.
:param info_dict: The dictionary to inspect.
:param error_msg: The error message to prefix before printing the
information about missing parameters.
:param param_prefix: Add this prefix to each parameter for error messages
:raises: MissingParameterValue, if one or more parameters are
empty in the provided dictionary.
"""
missing_info = []
for label, value in info_dict.items():
if not value:
missing_info.append(param_prefix + label)
if missing_info:
exc_msg = _("%(error_msg)s. The following parameters were "
"not passed to ironic: %(missing_info)s")
raise exception.MissingParameterValue(exc_msg %
{'error_msg': error_msg, 'missing_info': missing_info})
def fetch_images(ctx, cache, images_info):
"""Check for available disk space and fetch images using ImageCache.
:param ctx: context
:param cache: ImageCache instance to use for fetching
:param images_info: list of tuples (image uuid, destination path)
:raises: InstanceDeployFailure if unable to find enough disk space
"""
try:
image_cache.clean_up_caches(ctx, cache.master_dir, images_info)
except exception.InsufficientDiskSpace as e:
raise exception.InstanceDeployFailure(reason=e)
# NOTE(dtantsur): This code can suffer from race condition,
# if disk space is used between the check and actual download.
# This is probably unavoidable, as we can't control other
# (probably unrelated) processes
for uuid, path in images_info:
cache.fetch_image(uuid, path, ctx=ctx)

View File

@ -5,7 +5,7 @@ dhcp
goto deploy
:deploy
kernel {{ pxe_options.deployment_aki_path }} selinux=0 disk=cciss/c0d0,sda,hda,vda iscsi_target_iqn={{ pxe_options.deployment_iscsi_iqn }} deployment_id={{ pxe_options.deployment_id }} deployment_key={{ pxe_options.deployment_key }} ironic_api_url={{ pxe_options.ironic_api_url }} troubleshoot=0 text {{ pxe_options.pxe_append_params|default("", true) }} ip=${ip}:${next-server}:${gateway}:${netmask} BOOTIF=${mac}
kernel {{ pxe_options.deployment_aki_path }} selinux=0 disk={{ pxe_options.disk }} iscsi_target_iqn={{ pxe_options.iscsi_target_iqn }} deployment_id={{ pxe_options.deployment_id }} deployment_key={{ pxe_options.deployment_key }} ironic_api_url={{ pxe_options.ironic_api_url }} troubleshoot=0 text {{ pxe_options.pxe_append_params|default("", true) }} ip=${ip}:${next-server}:${gateway}:${netmask} BOOTIF=${mac}
initrd {{ pxe_options.deployment_ari_path }}
boot

View File

@ -0,0 +1,410 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
from oslo.config import cfg
from ironic.common import exception
from ironic.common import i18n
from ironic.common import image_service as service
from ironic.common import keystone
from ironic.common import states
from ironic.common import utils
from ironic.conductor import utils as manager_utils
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules import image_cache
from ironic.drivers import utils as driver_utils
from ironic.openstack.common import fileutils
from ironic.openstack.common import log as logging
from ironic.openstack.common import strutils
LOG = logging.getLogger(__name__)
_LE = i18n._LE
_LI = i18n._LI
# NOTE(rameshg87): This file now registers some of opts in pxe group.
# This is acceptable for now as a future refactoring into
# separate boot and deploy interfaces is planned, and moving config
# options twice is not recommended. Hence we would move the parameters
# to the appropriate place in the final refactoring.
pxe_opts = [
cfg.StrOpt('pxe_append_params',
default='nofb nomodeset vga=normal',
help='Additional append parameters for baremetal PXE boot.'),
cfg.StrOpt('default_ephemeral_format',
default='ext4',
help='Default file system format for ephemeral partition, '
'if one is created.'),
cfg.StrOpt('images_path',
default='/var/lib/ironic/images/',
help='Directory where images are stored on disk.'),
cfg.StrOpt('instance_master_path',
default='/var/lib/ironic/master_images',
help='Directory where master instance images are stored on '
'disk.'),
cfg.IntOpt('image_cache_size',
default=20480,
help='Maximum size (in MiB) of cache for master images, '
'including those in use.'),
# 10080 here is 1 week - 60*24*7. It is entirely arbitrary in the absence
# of a facility to disable the ttl entirely.
cfg.IntOpt('image_cache_ttl',
default=10080,
help='Maximum TTL (in minutes) for old master images in '
'cache.'),
cfg.StrOpt('disk_devices',
default='cciss/c0d0,sda,hda,vda',
help='The disk devices to scan while doing the deploy.'),
]
CONF = cfg.CONF
CONF.register_opts(pxe_opts, group='pxe')
@image_cache.cleanup(priority=50)
class InstanceImageCache(image_cache.ImageCache):
def __init__(self, image_service=None):
super(self.__class__, self).__init__(
CONF.pxe.instance_master_path,
# MiB -> B
cache_size=CONF.pxe.image_cache_size * 1024 * 1024,
# min -> sec
cache_ttl=CONF.pxe.image_cache_ttl * 60,
image_service=image_service)
def _get_image_dir_path(node_uuid):
"""Generate the dir for an instances disk."""
return os.path.join(CONF.pxe.images_path, node_uuid)
def _get_image_file_path(node_uuid):
"""Generate the full path for an instances disk."""
return os.path.join(_get_image_dir_path(node_uuid), 'disk')
def parse_instance_info(node):
"""Gets the instance specific Node deployment info.
This method validates whether the 'instance_info' property of the
supplied node contains the required information for this driver to
deploy images to the node.
:param node: a single Node.
:returns: A dict with the instance_info values.
:raises: MissingParameterValue, if any of the required parameters are
missing.
:raises: InvalidParameterValue, if any of the parameters have invalid
value.
"""
info = node.instance_info
i_info = {}
i_info['image_source'] = info.get('image_source')
i_info['root_gb'] = info.get('root_gb')
error_msg = _("Cannot validate iSCSI deploy")
deploy_utils.check_for_missing_params(i_info, error_msg)
# Internal use only
i_info['deploy_key'] = info.get('deploy_key')
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')
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]
raise exception.InvalidParameterValue(err_msg_invalid %
{'param': param, 'reason': reason})
if i_info['ephemeral_gb'] and not i_info['ephemeral_format']:
i_info['ephemeral_format'] = CONF.pxe.default_ephemeral_format
preserve_ephemeral = info.get('preserve_ephemeral', False)
try:
i_info['preserve_ephemeral'] = strutils.bool_from_string(
preserve_ephemeral, strict=True)
except ValueError as e:
raise exception.InvalidParameterValue(err_msg_invalid %
{'param': 'preserve_ephemeral', 'reason': e})
return i_info
def check_image_size(task):
"""Check if the requested image is larger than the root partition size.
:param task: a TaskManager instance containing the node to act on.
:raises: InstanceDeployFailure if size of the image is greater than root
partition.
"""
i_info = parse_instance_info(task.node)
image_path = _get_image_file_path(task.node.uuid)
image_mb = deploy_utils.get_image_mb(image_path)
root_mb = 1024 * int(i_info['root_gb'])
if image_mb > root_mb:
msg = (_('Root partition is too small for requested image. '
'Image size: %(image_mb)d MB, Root size: %(root_mb)d MB')
% {'image_mb': image_mb, 'root_mb': root_mb})
raise exception.InstanceDeployFailure(msg)
def cache_instance_image(ctx, node):
"""Fetch the instance's image from Glance
This method pulls the AMI and writes them to the appropriate place
on local disk.
:param ctx: context
:param node: an ironic node object
:returns: a tuple containing the uuid of the image and the path in
the filesystem where image is cached.
"""
i_info = parse_instance_info(node)
fileutils.ensure_tree(_get_image_dir_path(node.uuid))
image_path = _get_image_file_path(node.uuid)
uuid = i_info['image_source']
LOG.debug("Fetching image %(ami)s for node %(uuid)s",
{'ami': uuid, 'uuid': node.uuid})
deploy_utils.fetch_images(ctx, InstanceImageCache(), [(uuid, image_path)])
return (uuid, image_path)
def destroy_images(node_uuid):
"""Delete instance's image file.
:param node_uuid: the uuid of the ironic node.
"""
utils.unlink_without_raise(_get_image_file_path(node_uuid))
utils.rmtree_without_raise(_get_image_dir_path(node_uuid))
InstanceImageCache().clean_up()
def get_deploy_info(node, **kwargs):
"""Returns the information required for doing iSCSI deploy in a
dictionary.
:param node: ironic node object
:param kwargs: the keyword args passed from the conductor node.
:raises: MissingParameterValue, if some required parameters were not
passed.
:raises: InvalidParameterValue, if any of the parameters have invalid
value.
"""
deploy_key = kwargs.get('key')
i_info = parse_instance_info(node)
if i_info['deploy_key'] != deploy_key:
raise exception.InvalidParameterValue(_("Deploy key does not match"))
params = {'address': kwargs.get('address'),
'port': kwargs.get('port', '3260'),
'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,
}
missing = [key for key in params if params[key] is None]
if missing:
raise exception.MissingParameterValue(_(
"Parameters %s were not passed to ironic"
" for deploy.") % missing)
# ephemeral_format is nullable
params['ephemeral_format'] = i_info.get('ephemeral_format')
return params
def set_failed_state(task, msg):
"""Sets the deploy status as failed with relevant messages.
This method sets the deployment as fail with the given message.
It sets node's provision_state to DEPLOYFAIL and updates last_error
with the given error message. It also powers off the baremetal node.
:param task: a TaskManager instance containing the node to act on.
:param msg: the message to set in last_error of the node.
"""
node = task.node
node.provision_state = states.DEPLOYFAIL
node.target_provision_state = states.NOSTATE
node.save(task.context)
try:
manager_utils.node_power_action(task, states.POWER_OFF)
except Exception:
msg2 = (_('Node %s failed to power off while handling deploy '
'failure. This may be a serious condition. Node '
'should be removed from Ironic or put in maintenance '
'mode until the problem is resolved.') % node.uuid)
LOG.exception(msg2)
finally:
# NOTE(deva): node_power_action() erases node.last_error
# so we need to set it again here.
node.last_error = msg
node.save(task.context)
def continue_deploy(task, **kwargs):
"""Resume a deployment upon getting POST data from deploy ramdisk.
This method raises no exceptions because it is intended to be
invoked asynchronously as a callback from the deploy ramdisk.
:param task: a TaskManager instance containing the node to act on.
:param kwargs: the kwargs to be passed to deploy.
:returns: UUID of the root partition or None on error.
"""
node = task.node
node.provision_state = states.DEPLOYING
node.save(task.context)
params = get_deploy_info(node, **kwargs)
ramdisk_error = kwargs.get('error')
if ramdisk_error:
LOG.error(_LE('Error returned from deploy ramdisk: %s'),
ramdisk_error)
set_failed_state(task, _('Failure in deploy ramdisk.'))
destroy_images(node.uuid)
return
LOG.info(_LI('Continuing deployment for node %(node)s, params %(params)s'),
{'node': node.uuid, 'params': params})
root_uuid = None
try:
root_uuid = deploy_utils.deploy(**params)
except Exception as e:
LOG.error(_LE('Deploy failed for instance %(instance)s. '
'Error: %(error)s'),
{'instance': node.instance_uuid, 'error': e})
set_failed_state(task, _('Failed to continue iSCSI deployment.'))
destroy_images(node.uuid)
return root_uuid
def build_deploy_ramdisk_options(node, ctx):
"""Build the ramdisk config options for a node
This method builds the ramdisk options for a node,
given all the required parameters for doing iscsi deploy.
:param node: a single Node.
:param ctx: security context
:returns: A dictionary of options to be passed to ramdisk for performing
the deploy.
"""
# NOTE: we should strip '/' from the end because this is intended for
# hardcoded ramdisk script
ironic_api = (CONF.conductor.api_url or
keystone.get_service_url()).rstrip('/')
deploy_key = utils.random_alnum(32)
i_info = node.instance_info
i_info['deploy_key'] = deploy_key
node.instance_info = i_info
node.save(ctx)
deploy_options = {
'deployment_id': node['uuid'],
'deployment_key': deploy_key,
'iscsi_target_iqn': "iqn-%s" % node.uuid,
'ironic_api_url': ironic_api,
'disk': CONF.pxe.disk_devices,
}
return deploy_options
def validate_glance_image_properties(ctx, deploy_info, properties):
"""Validate the image in Glance.
Check if the image exist in Glance and if it contains the
properties passed.
:param ctx: security context
:param deploy_info: the deploy_info to be validated
:param properties: the list of image meta-properties to be validated.
:raises: InvalidParameterValue if the glance image doesn't exist.
:raises: MissingParameterValue if the glance image doesn't contain
the mentioned properties.
"""
image_id = deploy_info['image_source']
try:
glance_service = service.Service(version=1, context=ctx)
image_props = glance_service.show(image_id)['properties']
except (exception.GlanceConnectionFailed,
exception.ImageNotAuthorized,
exception.Invalid):
raise exception.InvalidParameterValue(_(
"Failed to connect to Glance to get the properties "
"of the image %s") % image_id)
except exception.ImageNotFound:
raise exception.InvalidParameterValue(_(
"Image %s not found in Glance") % image_id)
missing_props = []
for prop in properties:
if not image_props.get(prop):
missing_props.append(prop)
if missing_props:
props = ', '.join(missing_props)
raise exception.MissingParameterValue(_(
"Image %(image)s is missing the following properties: "
"%(properties)s") % {'image': image_id, 'properties': props})
def validate(task):
"""Validates the pre-requisites for iSCSI deploy.
Validates whether node in the task provided has some ports enrolled.
This method validates whether conductor url is available either from CONF
file or from keystone.
:param task: a TaskManager instance containing the node to act on.
:raises: InvalidParameterValue if no ports are enrolled for the given node.
"""
node = task.node
if not driver_utils.get_node_mac_addresses(task):
raise exception.InvalidParameterValue(_("Node %s does not have "
"any port associated with it.") % node.uuid)
try:
# TODO(lucasagomes): Validate the format of the URL
CONF.conductor.api_url or keystone.get_service_url()
except (exception.CatalogFailure,
exception.CatalogNotFound,
exception.CatalogUnauthorized):
raise exception.InvalidParameterValue(_(
"Couldn't get the URL of the Ironic API service from the "
"configuration file or keystone catalog."))

View File

@ -21,11 +21,10 @@ import os
import shutil
from oslo.config import cfg
from oslo.utils import strutils
from ironic.common import exception
from ironic.common import i18n
from ironic.common import image_service as service
from ironic.common import keystone
from ironic.common import neutron
from ironic.common import paths
from ironic.common import pxe_utils
@ -36,26 +35,16 @@ from ironic.conductor import utils as manager_utils
from ironic.drivers import base
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules import image_cache
from ironic.drivers import utils as driver_utils
from ironic.drivers.modules import iscsi_deploy
from ironic.openstack.common import fileutils
from ironic.openstack.common import log as logging
pxe_opts = [
cfg.StrOpt('pxe_append_params',
default='nofb nomodeset vga=normal',
help='Additional append parameters for baremetal PXE boot.'),
cfg.StrOpt('pxe_config_template',
default=paths.basedir_def(
'drivers/modules/pxe_config.template'),
help='Template file for PXE configuration.'),
cfg.StrOpt('default_ephemeral_format',
default='ext4',
help='Default file system format for ephemeral partition, '
'if one is created.'),
cfg.StrOpt('images_path',
default='/var/lib/ironic/images/',
help='Directory where images are stored on disk.'),
cfg.StrOpt('tftp_server',
default='$my_ip',
help='IP address of Ironic compute node\'s tftp server.'),
@ -65,25 +54,11 @@ pxe_opts = [
cfg.StrOpt('tftp_master_path',
default='/tftpboot/master_images',
help='Directory where master tftp images are stored on disk.'),
cfg.StrOpt('instance_master_path',
default='/var/lib/ironic/master_images',
help='Directory where master instance images are stored on '
'disk.'),
# NOTE(dekehn): Additional boot files options may be created in the event
# other architectures require different boot files.
cfg.StrOpt('pxe_bootfile_name',
default='pxelinux.0',
help='Neutron bootfile DHCP parameter.'),
cfg.IntOpt('image_cache_size',
default=20480,
help='Maximum size (in MiB) of cache for master images, '
'including those in use.'),
# 10080 here is 1 week - 60*24*7. It is entirely arbitrary in the absence
# of a facility to disable the ttl entirely.
cfg.IntOpt('image_cache_ttl',
default=10080,
help='Maximum TTL (in minutes) for old master images in '
'cache.'),
cfg.StrOpt('http_url',
help='Ironic compute node\'s HTTP server URL. '
'Example: http://192.1.2.3:8080'),
@ -101,10 +76,14 @@ pxe_opts = [
LOG = logging.getLogger(__name__)
_LE = i18n._LE
_LI = i18n._LI
CONF = cfg.CONF
CONF.register_opts(pxe_opts, group='pxe')
CONF.import_opt('use_ipv6', 'ironic.netconf')
REQUIRED_PROPERTIES = {
'pxe_deploy_kernel': _("UUID (from Glance) of the deployment kernel. "
"Required."),
@ -114,18 +93,6 @@ REQUIRED_PROPERTIES = {
COMMON_PROPERTIES = REQUIRED_PROPERTIES
def _check_for_missing_params(info_dict, param_prefix=''):
missing_info = []
for label, value in info_dict.items():
if not value:
missing_info.append(param_prefix + label)
if missing_info:
raise exception.MissingParameterValue(_(
"Can not validate PXE bootloader. The following parameters "
"were not passed to ironic: %s") % missing_info)
def _parse_driver_info(node):
"""Gets the driver specific Node deployment info.
@ -142,61 +109,12 @@ def _parse_driver_info(node):
d_info['deploy_kernel'] = info.get('pxe_deploy_kernel')
d_info['deploy_ramdisk'] = info.get('pxe_deploy_ramdisk')
_check_for_missing_params(d_info, 'pxe_')
error_msg = _("Cannot validate PXE bootloader")
deploy_utils.check_for_missing_params(d_info, error_msg, 'pxe_')
return d_info
def _parse_instance_info(node):
"""Gets the instance specific Node deployment info.
This method validates whether the 'instance_info' property of the
supplied node contains the required information for this driver to
deploy images to the node.
:param node: a single Node.
:returns: A dict with the instance_info values.
:raises: MissingParameterValue
:raises: InvalidParameterValue
"""
info = node.instance_info
i_info = {}
i_info['image_source'] = info.get('image_source')
i_info['root_gb'] = info.get('root_gb')
_check_for_missing_params(i_info)
# Internal use only
i_info['deploy_key'] = info.get('deploy_key')
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')
err_msg_invalid = _("Can not validate PXE bootloader. 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]
raise exception.InvalidParameterValue(err_msg_invalid %
{'param': param, 'reason': reason})
if i_info['ephemeral_gb'] and not i_info['ephemeral_format']:
i_info['ephemeral_format'] = CONF.pxe.default_ephemeral_format
preserve_ephemeral = info.get('preserve_ephemeral', False)
try:
i_info['preserve_ephemeral'] = strutils.bool_from_string(
preserve_ephemeral, strict=True)
except ValueError as e:
raise exception.InvalidParameterValue(err_msg_invalid %
{'param': 'preserve_ephemeral', 'reason': e})
return i_info
def _parse_deploy_info(node):
"""Gets the instance and driver specific Node deployment info.
@ -210,7 +128,7 @@ def _parse_deploy_info(node):
:raises: InvalidParameterValue
"""
info = {}
info.update(_parse_instance_info(node))
info.update(iscsi_deploy.parse_instance_info(node))
info.update(_parse_driver_info(node))
return info
@ -230,17 +148,6 @@ def _build_pxe_config_options(node, pxe_info, ctx):
:returns: A dictionary of pxe options to be used in the pxe bootfile
template.
"""
# NOTE: we should strip '/' from the end because this is intended for
# hardcoded ramdisk script
ironic_api = (CONF.conductor.api_url or
keystone.get_service_url()).rstrip('/')
deploy_key = utils.random_alnum(32)
i_info = node.instance_info
i_info['deploy_key'] = deploy_key
node.instance_info = i_info
node.save(ctx)
if CONF.pxe.ipxe_enabled:
deploy_kernel = '/'.join([CONF.pxe.http_url, node.uuid,
'deploy_kernel'])
@ -255,38 +162,29 @@ def _build_pxe_config_options(node, pxe_info, ctx):
ramdisk = pxe_info['ramdisk'][1]
pxe_options = {
'deployment_id': node['uuid'],
'deployment_key': deploy_key,
'deployment_iscsi_iqn': "iqn-%s" % node.uuid,
'deployment_aki_path': deploy_kernel,
'deployment_ari_path': deploy_ramdisk,
'aki_path': kernel,
'ari_path': ramdisk,
'ironic_api_url': ironic_api,
'pxe_append_params': CONF.pxe.pxe_append_params,
}
deploy_ramdisk_options = iscsi_deploy.build_deploy_ramdisk_options(node,
ctx)
pxe_options.update(deploy_ramdisk_options)
return pxe_options
def _get_image_dir_path(node_uuid):
"""Generate the dir for an instances disk."""
return os.path.join(CONF.pxe.images_path, node_uuid)
def _get_image_file_path(node_uuid):
"""Generate the full path for an instances disk."""
return os.path.join(_get_image_dir_path(node_uuid), 'disk')
def _get_token_file_path(node_uuid):
"""Generate the path for PKI token file."""
return os.path.join(CONF.pxe.tftp_root, 'token-' + node_uuid)
class PXEImageCache(image_cache.ImageCache):
def __init__(self, master_dir, image_service=None):
super(PXEImageCache, self).__init__(
master_dir,
@image_cache.cleanup(priority=25)
class TFTPImageCache(image_cache.ImageCache):
def __init__(self, image_service=None):
super(TFTPImageCache, self).__init__(
CONF.pxe.tftp_master_path,
# MiB -> B
cache_size=CONF.pxe.image_cache_size * 1024 * 1024,
# min -> sec
@ -294,77 +192,13 @@ class PXEImageCache(image_cache.ImageCache):
image_service=image_service)
@image_cache.cleanup(priority=25)
class TFTPImageCache(PXEImageCache):
def __init__(self, image_service=None):
super(TFTPImageCache, self).__init__(CONF.pxe.tftp_master_path)
@image_cache.cleanup(priority=50)
class InstanceImageCache(PXEImageCache):
def __init__(self, image_service=None):
super(InstanceImageCache, self).__init__(CONF.pxe.instance_master_path)
def _fetch_images(ctx, cache, images_info):
"""Check for available disk space and fetch images using ImageCache.
:param ctx: context
:param cache: ImageCache instance to use for fetching
:param images_info: list of tuples (image uuid, destination path)
:raises: InstanceDeployFailure if unable to find enough disk space
"""
try:
image_cache.clean_up_caches(ctx, cache.master_dir, images_info)
except exception.InsufficientDiskSpace as e:
raise exception.InstanceDeployFailure(reason=e)
# NOTE(dtantsur): This code can suffer from race condition,
# if disk space is used between the check and actual download.
# This is probably unavoidable, as we can't control other
# (probably unrelated) processes
for uuid, path in images_info:
cache.fetch_image(uuid, path, ctx=ctx)
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",
node.uuid)
_fetch_images(ctx, TFTPImageCache(), pxe_info.values())
def _cache_instance_image(ctx, node):
"""Fetch the instance's image from Glance
This method pulls the relevant AMI and associated kernel and ramdisk,
and the deploy kernel and ramdisk from Glance, and writes them
to the appropriate places on local disk.
Both sets of kernel and ramdisk are needed for PXE booting, so these
are stored under CONF.pxe.tftp_root.
At present, the AMI is cached and certain files are injected.
Debian/ubuntu-specific assumptions are made regarding the injected
files. In a future revision, this functionality will be replaced by a
more scalable and os-agnostic approach: the deployment ramdisk will
fetch from Glance directly, and write its own last-mile configuration.
"""
i_info = _parse_instance_info(node)
fileutils.ensure_tree(_get_image_dir_path(node.uuid))
image_path = _get_image_file_path(node.uuid)
uuid = i_info['image_source']
LOG.debug("Fetching image %(ami)s for node %(uuid)s" %
{'ami': uuid, 'uuid': node.uuid})
_fetch_images(ctx, InstanceImageCache(), [(uuid, image_path)])
return (uuid, image_path)
deploy_utils.fetch_images(ctx, TFTPImageCache(), pxe_info.values())
def _get_image_info(node, ctx):
@ -401,13 +235,6 @@ def _get_image_info(node, ctx):
return image_info
def _destroy_images(node_uuid):
"""Delete instance's image file."""
utils.unlink_without_raise(_get_image_file_path(node_uuid))
utils.rmtree_without_raise(_get_image_dir_path(node_uuid))
InstanceImageCache().clean_up()
def _create_token_file(task):
"""Save PKI token to file."""
token_file_path = _get_token_file_path(task.node.uuid)
@ -424,54 +251,6 @@ def _destroy_token_file(node):
utils.unlink_without_raise(token_file_path)
def _check_image_size(task):
"""Check if the requested image is larger than the root partition size."""
i_info = _parse_instance_info(task.node)
image_path = _get_image_file_path(task.node.uuid)
image_mb = deploy_utils.get_image_mb(image_path)
root_mb = 1024 * int(i_info['root_gb'])
if image_mb > root_mb:
msg = (_('Root partition is too small for requested image. '
'Image size: %(image_mb)d MB, Root size: %(root_mb)d MB')
% {'image_mb': image_mb, 'root_mb': root_mb})
raise exception.InstanceDeployFailure(msg)
def _validate_glance_image(ctx, deploy_info):
"""Validate the image in Glance.
Check if the image exist in Glance and if it contains the
'kernel_id' and 'ramdisk_id' properties.
:raises: InvalidParameterValue.
:raises: MissingParameterValue
"""
image_id = deploy_info['image_source']
try:
glance_service = service.Service(version=1, context=ctx)
image_props = glance_service.show(image_id)['properties']
except (exception.GlanceConnectionFailed,
exception.ImageNotAuthorized,
exception.Invalid):
raise exception.InvalidParameterValue(_(
"Failed to connect to Glance to get the properties "
"of the image %s") % image_id)
except exception.ImageNotFound:
raise exception.InvalidParameterValue(_(
"Image %s not found in Glance") % image_id)
missing_props = []
for prop in ('kernel_id', 'ramdisk_id'):
if not image_props.get(prop):
missing_props.append(prop)
if missing_props:
props = ', '.join(missing_props)
raise exception.MissingParameterValue(_(
"Image %(image)s is missing the following properties: "
"%(properties)s") % {'image': image_id, 'properties': props})
class PXEDeploy(base.DeployInterface):
"""PXE Deploy Interface: just a stub until the real driver is ported."""
@ -485,31 +264,19 @@ class PXEDeploy(base.DeployInterface):
:raises: InvalidParameterValue.
:raises: MissingParameterValue
"""
node = task.node
if not driver_utils.get_node_mac_addresses(task):
raise exception.MissingParameterValue(_("Node %s does not have "
"any port associated with it.") % node.uuid)
d_info = _parse_deploy_info(node)
if CONF.pxe.ipxe_enabled:
if not CONF.pxe.http_url or not CONF.pxe.http_root:
raise exception.MissingParameterValue(_(
"iPXE boot is enabled but no HTTP URL or HTTP "
"root was specified."))
# Try to get the URL of the Ironic API
try:
# TODO(lucasagomes): Validate the format of the URL
CONF.conductor.api_url or keystone.get_service_url()
except (exception.CatalogFailure,
exception.CatalogNotFound,
exception.CatalogUnauthorized):
raise exception.InvalidParameterValue(_(
"Couldn't get the URL of the Ironic API service from the "
"configuration file or keystone catalog."))
d_info = _parse_deploy_info(task.node)
_validate_glance_image(task.context, d_info)
iscsi_deploy.validate(task)
props = ['kernel_id', 'ramdisk_id']
iscsi_deploy.validate_glance_image_properties(task.context, d_info,
props)
@task_manager.require_exclusive_lock
def deploy(self, task):
@ -525,8 +292,8 @@ class PXEDeploy(base.DeployInterface):
:param task: a TaskManager instance containing the node to act on.
:returns: deploy state DEPLOYING.
"""
_cache_instance_image(task.context, task.node)
_check_image_size(task)
iscsi_deploy.cache_instance_image(task.context, task.node)
iscsi_deploy.check_image_size(task)
# TODO(yuriyz): more secure way needed for pass auth token
# to deploy ramdisk
@ -591,7 +358,7 @@ class PXEDeploy(base.DeployInterface):
pxe_utils.clean_up_pxe_config(task)
_destroy_images(node.uuid)
iscsi_deploy.destroy_images(node.uuid)
_destroy_token_file(node)
def take_over(self, task):
@ -602,46 +369,24 @@ class PXEDeploy(base.DeployInterface):
class VendorPassthru(base.VendorInterface):
"""Interface to mix IPMI and PXE vendor-specific interfaces."""
def _get_deploy_info(self, node, **kwargs):
d_info = _parse_deploy_info(node)
deploy_key = kwargs.get('key')
if d_info['deploy_key'] != deploy_key:
raise exception.InvalidParameterValue(_("Deploy key does not"
" match"))
params = {'address': kwargs.get('address'),
'port': kwargs.get('port', '3260'),
'iqn': kwargs.get('iqn'),
'lun': kwargs.get('lun', '1'),
'image_path': _get_image_file_path(node.uuid),
'pxe_config_path':
pxe_utils.get_pxe_config_file_path(node.uuid),
'root_mb': 1024 * int(d_info['root_gb']),
'swap_mb': int(d_info['swap_mb']),
'ephemeral_mb': 1024 * int(d_info['ephemeral_gb']),
'preserve_ephemeral': d_info['preserve_ephemeral'],
'node_uuid': node.uuid,
}
missing = [key for key in params.keys() if params[key] is None]
if missing:
raise exception.MissingParameterValue(_(
"Parameters %s were not passed to ironic"
" for deploy.") % missing)
# ephemeral_format is nullable
params['ephemeral_format'] = d_info.get('ephemeral_format')
return params
def get_properties(self):
return COMMON_PROPERTIES
def validate(self, task, **kwargs):
"""Validates the inputs for a vendor passthru.
This method checks whether the vendor passthru method is a valid one,
and then validates whether the required information for executing the
vendor passthru has been provided or not.
:param task: a TaskManager instance containing the node to act on.
:param kwargs: kwargs containins the method name and its parameters.
:raises: InvalidParameterValue if method is invalid or any parameters
to the method is invalid.
"""
method = kwargs['method']
if method == 'pass_deploy_info':
self._get_deploy_info(task.node, **kwargs)
iscsi_deploy.get_deploy_info(task.node, **kwargs)
else:
raise exception.InvalidParameterValue(_(
"Unsupported method (%s) passed to PXE driver.")
@ -649,69 +394,51 @@ class VendorPassthru(base.VendorInterface):
@task_manager.require_exclusive_lock
def _continue_deploy(self, task, **kwargs):
"""Resume a deployment upon getting POST data from deploy ramdisk.
"""Continues the deployment of baremetal node over iSCSI.
This method raises no exceptions because it is intended to be
invoked asynchronously as a callback from the deploy ramdisk.
This method continues the deployment of the baremetal node over iSCSI
from where the deployment ramdisk has left off.
:param task: a TaskManager instance containing the node to act on.
:param kwargs: kwargs for performing iscsi deployment.
"""
node = task.node
def _set_failed_state(msg):
node.provision_state = states.DEPLOYFAIL
node.target_provision_state = states.NOSTATE
node.save(task.context)
try:
manager_utils.node_power_action(task, states.POWER_OFF)
except Exception:
msg = (_('Node %s failed to power off while handling deploy '
'failure. This may be a serious condition. Node '
'should be removed from Ironic or put in maintenance '
'mode until the problem is resolved.') % node.uuid)
LOG.error(msg)
finally:
# NOTE(deva): node_power_action() erases node.last_error
# so we need to set it again here.
node.last_error = msg
node.save(task.context)
if node.provision_state != states.DEPLOYWAIT:
LOG.error(_('Node %s is not waiting to be deployed.') %
node.uuid)
LOG.error(_LE('Node %s is not waiting to be deployed.'), node.uuid)
return
node.provision_state = states.DEPLOYING
node.save(task.context)
# remove cached keystone token immediately
_destroy_token_file(node)
params = self._get_deploy_info(node, **kwargs)
ramdisk_error = kwargs.get('error')
root_uuid = iscsi_deploy.continue_deploy(task, **kwargs)
if ramdisk_error:
LOG.error(_('Error returned from PXE deploy ramdisk: %s')
% ramdisk_error)
_set_failed_state(_('Failure in PXE deploy ramdisk.'))
_destroy_images(node.uuid)
if not root_uuid:
return
LOG.info(_('Continuing deployment for node %(node)s, params '
'%(params)s') % {'node': node.uuid, 'params': params})
try:
deploy_utils.deploy(**params)
except Exception as e:
LOG.error(_('PXE deploy failed for instance %(instance)s. '
'Error: %(error)s') % {'instance': node.instance_uuid,
'error': e})
_set_failed_state(_('PXE driver failed to continue deployment.'))
else:
LOG.info(_('Deployment to node %s done') % node.uuid)
pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid)
deploy_utils.switch_pxe_config(pxe_config_path, root_uuid)
deploy_utils.notify_deploy_complete(kwargs['address'])
LOG.info(_LI('Deployment to node %s done'), node.uuid)
node.provision_state = states.ACTIVE
node.target_provision_state = states.NOSTATE
node.save(task.context)
except Exception as e:
_destroy_images(node.uuid)
LOG.error(_LE('Deploy failed for instance %(instance)s. '
'Error: %(error)s'),
{'instance': node.instance_uuid, 'error': e})
msg = _('Failed to continue iSCSI deployment.')
iscsi_deploy.set_failed_state(task, msg)
def vendor_passthru(self, task, **kwargs):
"""Invokes a vendor passthru method.
:param task: a TaskManager instance containing the node to act on.
:param kwargs: kwargs containins the method name and its parameters.
"""
method = kwargs['method']
if method == 'pass_deploy_info':
self._continue_deploy(task, **kwargs)

View File

@ -2,7 +2,7 @@ default deploy
label deploy
kernel {{ pxe_options.deployment_aki_path }}
append initrd={{ pxe_options.deployment_ari_path }} rootfstype=ramfs selinux=0 disk=cciss/c0d0,sda,hda,vda iscsi_target_iqn={{ pxe_options.deployment_iscsi_iqn }} deployment_id={{ pxe_options.deployment_id }} deployment_key={{ pxe_options.deployment_key }} ironic_api_url={{ pxe_options.ironic_api_url }} troubleshoot=0 text {{ pxe_options.pxe_append_params|default("", true) }}
append initrd={{ pxe_options.deployment_ari_path }} rootfstype=ramfs selinux=0 disk={{ pxe_options.disk }} iscsi_target_iqn={{ pxe_options.iscsi_target_iqn }} deployment_id={{ pxe_options.deployment_id }} deployment_key={{ pxe_options.deployment_key }} ironic_api_url={{ pxe_options.ironic_api_url }} troubleshoot=0 text {{ pxe_options.pxe_append_params|default("", true) }}
ipappend 3

View File

@ -25,6 +25,7 @@ from ironic.common import disk_partitioner
from ironic.common import exception
from ironic.common import utils as common_utils
from ironic.drivers.modules import deploy_utils as utils
from ironic.drivers.modules import image_cache
from ironic.openstack.common import processutils
from ironic.tests import base as tests_base
@ -118,7 +119,6 @@ class PhysicalWorkTestCase(tests_base.TestCase):
iqn = 'iqn.xyz'
lun = 1
image_path = '/tmp/xyz/image'
pxe_config_path = '/tmp/abc/pxeconfig'
root_mb = 128
swap_mb = 64
ephemeral_mb = 0
@ -133,7 +133,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi',
'logout_iscsi', 'delete_iscsi', 'make_partitions',
'is_block_device', 'dd', 'mkswap', 'block_uuid',
'switch_pxe_config', 'notify', 'destroy_disk_metadata']
'notify', 'destroy_disk_metadata']
parent_mock = self._mock_calls(name_list)
parent_mock.get_dev.return_value = dev
parent_mock.get_image_mb.return_value = 1
@ -156,16 +156,15 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock.call.mkswap(swap_part),
mock.call.block_uuid(root_part),
mock.call.logout_iscsi(address, port, iqn),
mock.call.delete_iscsi(address, port, iqn),
mock.call.switch_pxe_config(pxe_config_path,
root_uuid),
mock.call.notify(address, 10000)]
mock.call.delete_iscsi(address, port, iqn)]
utils.deploy(address, port, iqn, lun, image_path, pxe_config_path,
root_mb, swap_mb, ephemeral_mb, ephemeral_format,
node_uuid)
returned_root_uuid = utils.deploy(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_without_swap(self):
"""Check loosely all functions are called with right args."""
@ -174,7 +173,6 @@ class PhysicalWorkTestCase(tests_base.TestCase):
iqn = 'iqn.xyz'
lun = 1
image_path = '/tmp/xyz/image'
pxe_config_path = '/tmp/abc/pxeconfig'
root_mb = 128
swap_mb = 0
ephemeral_mb = 0
@ -188,7 +186,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi',
'logout_iscsi', 'delete_iscsi', 'make_partitions',
'is_block_device', 'dd', 'block_uuid',
'switch_pxe_config', 'notify', 'destroy_disk_metadata']
'notify', 'destroy_disk_metadata']
parent_mock = self._mock_calls(name_list)
parent_mock.get_dev.return_value = dev
parent_mock.get_image_mb.return_value = 1
@ -208,16 +206,15 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock.call.dd(image_path, root_part),
mock.call.block_uuid(root_part),
mock.call.logout_iscsi(address, port, iqn),
mock.call.delete_iscsi(address, port, iqn),
mock.call.switch_pxe_config(pxe_config_path,
root_uuid),
mock.call.notify(address, 10000)]
mock.call.delete_iscsi(address, port, iqn)]
utils.deploy(address, port, iqn, lun, image_path, pxe_config_path,
root_mb, swap_mb, ephemeral_mb, ephemeral_format,
node_uuid)
returned_root_uuid = utils.deploy(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):
"""Check loosely all functions are called with right args."""
@ -226,7 +223,6 @@ class PhysicalWorkTestCase(tests_base.TestCase):
iqn = 'iqn.xyz'
lun = 1
image_path = '/tmp/xyz/image'
pxe_config_path = '/tmp/abc/pxeconfig'
root_mb = 128
swap_mb = 64
ephemeral_mb = 256
@ -242,7 +238,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi',
'logout_iscsi', 'delete_iscsi', 'make_partitions',
'is_block_device', 'dd', 'mkswap', 'block_uuid',
'switch_pxe_config', 'notify', 'mkfs_ephemeral',
'notify', 'mkfs_ephemeral',
'destroy_disk_metadata']
parent_mock = self._mock_calls(name_list)
parent_mock.get_dev.return_value = dev
@ -270,16 +266,15 @@ class PhysicalWorkTestCase(tests_base.TestCase):
ephemeral_format),
mock.call.block_uuid(root_part),
mock.call.logout_iscsi(address, port, iqn),
mock.call.delete_iscsi(address, port, iqn),
mock.call.switch_pxe_config(pxe_config_path,
root_uuid),
mock.call.notify(address, 10000)]
mock.call.delete_iscsi(address, port, iqn)]
utils.deploy(address, port, iqn, lun, image_path, pxe_config_path,
root_mb, swap_mb, ephemeral_mb, ephemeral_format,
node_uuid)
returned_root_uuid = utils.deploy(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):
"""Check if all functions are called with right args."""
@ -288,7 +283,6 @@ class PhysicalWorkTestCase(tests_base.TestCase):
iqn = 'iqn.xyz'
lun = 1
image_path = '/tmp/xyz/image'
pxe_config_path = '/tmp/abc/pxeconfig'
root_mb = 128
swap_mb = 64
ephemeral_mb = 256
@ -304,8 +298,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi',
'logout_iscsi', 'delete_iscsi', 'make_partitions',
'is_block_device', 'dd', 'mkswap', 'block_uuid',
'switch_pxe_config', 'notify', 'mkfs_ephemeral',
'get_dev_block_size']
'notify', 'mkfs_ephemeral', 'get_dev_block_size']
parent_mock = self._mock_calls(name_list)
parent_mock.get_dev.return_value = dev
parent_mock.get_image_mb.return_value = 1
@ -330,17 +323,16 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock.call.mkswap(swap_part),
mock.call.block_uuid(root_part),
mock.call.logout_iscsi(address, port, iqn),
mock.call.delete_iscsi(address, port, iqn),
mock.call.switch_pxe_config(pxe_config_path,
root_uuid),
mock.call.notify(address, 10000)]
mock.call.delete_iscsi(address, port, iqn)]
utils.deploy(address, port, iqn, lun, image_path, pxe_config_path,
root_mb, swap_mb, ephemeral_mb, ephemeral_format,
node_uuid, preserve_ephemeral=True)
returned_root_uuid = utils.deploy(address, port, iqn, lun,
image_path, root_mb, swap_mb,
ephemeral_mb, ephemeral_format,
node_uuid, preserve_ephemeral=True)
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)
def test_always_logout_and_delete_iscsi(self):
"""Check if logout_iscsi() and delete_iscsi() are called.
@ -354,7 +346,6 @@ class PhysicalWorkTestCase(tests_base.TestCase):
iqn = 'iqn.xyz'
lun = 1
image_path = '/tmp/xyz/image'
pxe_config_path = '/tmp/abc/pxeconfig'
root_mb = 128
swap_mb = 64
ephemeral_mb = 256
@ -393,8 +384,8 @@ class PhysicalWorkTestCase(tests_base.TestCase):
self.assertRaises(TestException, utils.deploy,
address, port, iqn, lun, image_path,
pxe_config_path, root_mb, swap_mb, ephemeral_mb,
ephemeral_format, node_uuid)
root_mb, swap_mb, ephemeral_mb, ephemeral_format,
node_uuid)
self.assertEqual(calls_expected, parent_mock.mock_calls)
@ -715,3 +706,30 @@ class RealFilePartitioningTestCase(tests_base.TestCase):
self.assertEqual([6, 3], sizes[:2],
"unexpected partitioning %s" % part_table)
self.assertIn(sizes[2], (9, 10))
@mock.patch.object(image_cache, 'clean_up_caches')
def test_fetch_images(self, mock_clean_up_caches):
mock_cache = mock.MagicMock(master_dir='master_dir')
utils.fetch_images(None, mock_cache, [('uuid', 'path')])
mock_clean_up_caches.assert_called_once_with(None, 'master_dir',
[('uuid', 'path')])
mock_cache.fetch_image.assert_called_once_with('uuid', 'path',
ctx=None)
@mock.patch.object(image_cache, 'clean_up_caches')
def test_fetch_images_fail(self, mock_clean_up_caches):
exc = exception.InsufficientDiskSpace(path='a',
required=2,
actual=1)
mock_cache = mock.MagicMock(master_dir='master_dir')
mock_clean_up_caches.side_effect = [exc]
self.assertRaises(exception.InstanceDeployFailure,
utils.fetch_images,
None,
mock_cache,
[('uuid', 'path')])
mock_clean_up_caches.assert_called_once_with(None, 'master_dir',
[('uuid', 'path')])

View File

@ -0,0 +1,205 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Test class for iSCSI deploy mechanism."""
import mock
import os
import tempfile
from oslo.config import cfg
from ironic.common import exception
from ironic.common import utils
from ironic.db import api as dbapi
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules import iscsi_deploy
from ironic.openstack.common import context
from ironic.openstack.common import fileutils
from ironic.tests import base
from ironic.tests.conductor import utils as mgr_utils
from ironic.tests.db import base as db_base
from ironic.tests.db import utils as db_utils
from ironic.tests.objects import utils as obj_utils
CONF = cfg.CONF
INST_INFO_DICT = db_utils.get_test_pxe_instance_info()
DRV_INFO_DICT = db_utils.get_test_pxe_driver_info()
class IscsiDeployValidateParametersTestCase(base.TestCase):
def setUp(self):
super(IscsiDeployValidateParametersTestCase, self).setUp()
self.context = context.get_admin_context()
self.dbapi = dbapi.get_instance()
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)
info = iscsi_deploy.parse_instance_info(node)
self.assertIsNotNone(info.get('image_source'))
self.assertIsNotNone(info.get('root_gb'))
self.assertEqual(0, info.get('ephemeral_gb'))
def test_parse_instance_info_missing_instance_source(self):
# 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)
self.assertRaises(exception.MissingParameterValue,
iscsi_deploy.parse_instance_info,
node)
def test_parse_instance_info_missing_root_gb(self):
# 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)
self.assertRaises(exception.MissingParameterValue,
iscsi_deploy.parse_instance_info,
node)
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)
self.assertRaises(exception.InvalidParameterValue,
iscsi_deploy.parse_instance_info,
node)
def test_parse_instance_info_valid_ephemeral_gb(self):
ephemeral_gb = 10
ephemeral_fmt = 'test-fmt'
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)
data = iscsi_deploy.parse_instance_info(node)
self.assertEqual(ephemeral_gb, data.get('ephemeral_gb'))
self.assertEqual(ephemeral_fmt, data.get('ephemeral_format'))
def test_parse_instance_info_invalid_ephemeral_gb(self):
info = dict(INST_INFO_DICT)
info['ephemeral_gb'] = 'foobar'
info['ephemeral_format'] = 'exttest'
node = obj_utils.create_test_node(self.context, instance_info=info)
self.assertRaises(exception.InvalidParameterValue,
iscsi_deploy.parse_instance_info,
node)
def test_parse_instance_info_valid_ephemeral_missing_format(self):
ephemeral_gb = 10
ephemeral_fmt = 'test-fmt'
info = dict(INST_INFO_DICT)
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)
instance_info = iscsi_deploy.parse_instance_info(node)
self.assertEqual(ephemeral_fmt, instance_info['ephemeral_format'])
def test_parse_instance_info_valid_preserve_ephemeral_true(self):
info = dict(INST_INFO_DICT)
for _id, opt in enumerate(['true', 'TRUE', 'True', 't',
'on', 'yes', 'y', '1']):
info['preserve_ephemeral'] = opt
node = obj_utils.create_test_node(self.context, id=_id,
uuid=utils.generate_uuid(),
instance_info=info)
data = iscsi_deploy.parse_instance_info(node)
self.assertTrue(data.get('preserve_ephemeral'))
def test_parse_instance_info_valid_preserve_ephemeral_false(self):
info = dict(INST_INFO_DICT)
for _id, opt in enumerate(['false', 'FALSE', 'False', 'f',
'off', 'no', 'n', '0']):
info['preserve_ephemeral'] = opt
node = obj_utils.create_test_node(self.context, id=_id,
uuid=utils.generate_uuid(),
instance_info=info)
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)
self.assertRaises(exception.InvalidParameterValue,
iscsi_deploy.parse_instance_info,
node)
class IscsiDeployPrivateMethodsTestCase(db_base.DbTestCase):
def setUp(self):
super(IscsiDeployPrivateMethodsTestCase, self).setUp()
n = {
'driver': 'fake_pxe',
'instance_info': INST_INFO_DICT,
'driver_info': DRV_INFO_DICT,
}
mgr_utils.mock_the_extension_manager(driver="fake_pxe")
self.dbapi = dbapi.get_instance()
self.context = context.get_admin_context()
self.node = obj_utils.create_test_node(self.context, **n)
def test__get_image_dir_path(self):
self.assertEqual(os.path.join(CONF.pxe.images_path,
self.node.uuid),
iscsi_deploy._get_image_dir_path(self.node.uuid))
def test__get_image_file_path(self):
self.assertEqual(os.path.join(CONF.pxe.images_path,
self.node.uuid,
'disk'),
iscsi_deploy._get_image_file_path(self.node.uuid))
class IscsiDeployMethodsTestCase(db_base.DbTestCase):
def setUp(self):
super(IscsiDeployMethodsTestCase, self).setUp()
n = {
'driver': 'fake_pxe',
'instance_info': INST_INFO_DICT,
'driver_info': DRV_INFO_DICT,
}
mgr_utils.mock_the_extension_manager(driver="fake_pxe")
self.dbapi = dbapi.get_instance()
self.context = context.get_admin_context()
self.node = obj_utils.create_test_node(self.context, **n)
@mock.patch.object(deploy_utils, 'fetch_images')
def test_cache_instance_images_master_path(self, mock_fetch_image):
temp_dir = tempfile.mkdtemp()
self.config(images_path=temp_dir, group='pxe')
self.config(instance_master_path=os.path.join(temp_dir,
'instance_master_path'),
group='pxe')
fileutils.ensure_tree(CONF.pxe.instance_master_path)
(uuid, image_path) = iscsi_deploy.cache_instance_image(None, self.node)
mock_fetch_image.assert_called_once_with(None,
mock.ANY,
[(uuid, image_path)])
self.assertEqual('glance://image_uuid', uuid)
self.assertEqual(os.path.join(temp_dir,
self.node.uuid,
'disk'),
image_path)

View File

@ -35,7 +35,7 @@ from ironic.conductor import task_manager
from ironic.conductor import utils as manager_utils
from ironic.db import api as dbapi
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules import image_cache
from ironic.drivers.modules import iscsi_deploy
from ironic.drivers.modules import pxe
from ironic.openstack.common import context
from ironic.openstack.common import fileutils
@ -46,7 +46,6 @@ from ironic.tests.db import base as db_base
from ironic.tests.db import utils as db_utils
from ironic.tests.objects import utils as obj_utils
CONF = cfg.CONF
INST_INFO_DICT = db_utils.get_test_pxe_instance_info()
@ -100,103 +99,6 @@ class PXEValidateParametersTestCase(base.TestCase):
self.assertIsNotNone(info.get('deploy_ramdisk'))
self.assertIsNotNone(info.get('deploy_kernel'))
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)
info = pxe._parse_instance_info(node)
self.assertIsNotNone(info.get('image_source'))
self.assertIsNotNone(info.get('root_gb'))
self.assertEqual(0, info.get('ephemeral_gb'))
def test__parse_instance_info_missing_instance_source(self):
# 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)
self.assertRaises(exception.MissingParameterValue,
pxe._parse_instance_info,
node)
def test__parse_instance_info_missing_root_gb(self):
# 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)
self.assertRaises(exception.MissingParameterValue,
pxe._parse_instance_info,
node)
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)
self.assertRaises(exception.InvalidParameterValue,
pxe._parse_instance_info,
node)
def test__parse_instance_info_valid_ephemeral_gb(self):
ephemeral_gb = 10
ephemeral_fmt = 'test-fmt'
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)
data = pxe._parse_instance_info(node)
self.assertEqual(ephemeral_gb, data.get('ephemeral_gb'))
self.assertEqual(ephemeral_fmt, data.get('ephemeral_format'))
def test__parse_instance_info_invalid_ephemeral_gb(self):
info = dict(INST_INFO_DICT)
info['ephemeral_gb'] = 'foobar'
info['ephemeral_format'] = 'exttest'
node = obj_utils.create_test_node(self.context, instance_info=info)
self.assertRaises(exception.InvalidParameterValue,
pxe._parse_instance_info,
node)
def test__parse_instance_info_valid_ephemeral_missing_format(self):
ephemeral_gb = 10
ephemeral_fmt = 'test-fmt'
info = dict(INST_INFO_DICT)
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)
instance_info = pxe._parse_instance_info(node)
self.assertEqual(ephemeral_fmt, instance_info['ephemeral_format'])
def test__parse_instance_info_valid_preserve_ephemeral_true(self):
info = dict(INST_INFO_DICT)
for _id, opt in enumerate(['true', 'TRUE', 'True', 't',
'on', 'yes', 'y', '1']):
info['preserve_ephemeral'] = opt
node = obj_utils.create_test_node(self.context, id=_id,
uuid=utils.generate_uuid(),
instance_info=info)
data = pxe._parse_instance_info(node)
self.assertTrue(data.get('preserve_ephemeral'))
def test__parse_instance_info_valid_preserve_ephemeral_false(self):
info = dict(INST_INFO_DICT)
for _id, opt in enumerate(['false', 'FALSE', 'False', 'f',
'off', 'no', 'n', '0']):
info['preserve_ephemeral'] = opt
node = obj_utils.create_test_node(self.context, id=_id,
uuid=utils.generate_uuid(),
instance_info=info)
data = pxe._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)
self.assertRaises(exception.InvalidParameterValue,
pxe._parse_instance_info,
node)
class PXEPrivateMethodsTestCase(db_base.DbTestCase):
@ -260,6 +162,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
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_key = '0123456789ABCDEFGHIJKLMNOPQRSTUV'
random_alnum_mock.return_value = fake_key
@ -290,7 +193,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
expected_options = {
'deployment_key': '0123456789ABCDEFGHIJKLMNOPQRSTUV',
'ari_path': ramdisk,
'deployment_iscsi_iqn': u'iqn-1be26c0b-03f2-4d2e-ae87-c02d7f33'
'iscsi_target_iqn': u'iqn-1be26c0b-03f2-4d2e-ae87-c02d7f33'
u'c123',
'deployment_ari_path': deploy_ramdisk,
'pxe_append_params': 'test_param',
@ -298,6 +201,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
'deployment_id': u'1be26c0b-03f2-4d2e-ae87-c02d7f33c123',
'ironic_api_url': 'http://192.168.122.184:6385',
'deployment_aki_path': deploy_kernel,
'disk': 'sda'
}
image_info = {'deploy_kernel': ('deploy_kernel',
@ -335,22 +239,12 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
def test__build_pxe_config_options_ipxe(self):
self._test_build_pxe_config_options(ipxe_enabled=True)
def test__get_image_dir_path(self):
self.assertEqual(os.path.join(CONF.pxe.images_path, self.node.uuid),
pxe._get_image_dir_path(self.node.uuid))
def test__get_image_file_path(self):
self.assertEqual(os.path.join(CONF.pxe.images_path,
self.node.uuid,
'disk'),
pxe._get_image_file_path(self.node.uuid))
def test_get_token_file_path(self):
node_uuid = self.node.uuid
self.assertEqual('/tftpboot/token-' + node_uuid,
pxe._get_token_file_path(node_uuid))
@mock.patch.object(pxe, '_fetch_images')
@mock.patch.object(deploy_utils, 'fetch_images')
def test__cache_tftp_images_master_path(self, mock_fetch_image):
temp_dir = tempfile.mkdtemp()
self.config(tftp_root=temp_dir, group='pxe')
@ -369,29 +263,9 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
[('deploy_kernel',
image_path)])
@mock.patch.object(pxe, '_fetch_images')
def test__cache_instance_images_master_path(self, mock_fetch_image):
temp_dir = tempfile.mkdtemp()
self.config(images_path=temp_dir, group='pxe')
self.config(instance_master_path=os.path.join(temp_dir,
'instance_master_path'),
group='pxe')
fileutils.ensure_tree(CONF.pxe.instance_master_path)
(uuid, image_path) = pxe._cache_instance_image(None,
self.node)
mock_fetch_image.assert_called_once_with(None,
mock.ANY,
[(uuid, image_path)])
self.assertEqual('glance://image_uuid', uuid)
self.assertEqual(os.path.join(temp_dir,
self.node.uuid,
'disk'),
image_path)
@mock.patch.object(pxe, 'TFTPImageCache', lambda: None)
@mock.patch.object(fileutils, 'ensure_tree')
@mock.patch.object(pxe, '_fetch_images')
@mock.patch.object(deploy_utils, 'fetch_images')
def test__cache_ramdisk_kernel(self, mock_fetch_image, mock_ensure_tree):
self.config(ipxe_enabled=False, group='pxe')
fake_pxe_info = {'foo': 'bar'}
@ -404,7 +278,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
@mock.patch.object(pxe, 'TFTPImageCache', lambda: None)
@mock.patch.object(fileutils, 'ensure_tree')
@mock.patch.object(pxe, '_fetch_images')
@mock.patch.object(deploy_utils, 'fetch_images')
def test__cache_ramdisk_kernel_ipxe(self, mock_fetch_image,
mock_ensure_tree):
self.config(ipxe_enabled=True, group='pxe')
@ -416,33 +290,6 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
mock_fetch_image.assert_called_once_with(self.context, mock.ANY,
fake_pxe_info.values())
@mock.patch.object(image_cache, 'clean_up_caches')
def test__fetch_images(self, mock_clean_up_caches):
mock_cache = mock.MagicMock(master_dir='master_dir')
pxe._fetch_images(None, mock_cache, [('uuid', 'path')])
mock_clean_up_caches.assert_called_once_with(None, 'master_dir',
[('uuid', 'path')])
mock_cache.fetch_image.assert_called_once_with('uuid', 'path',
ctx=None)
@mock.patch.object(image_cache, 'clean_up_caches')
def test__fetch_images_fail(self, mock_clean_up_caches):
exc = exception.InsufficientDiskSpace(path='a',
required=2,
actual=1)
mock_cache = mock.MagicMock(master_dir='master_dir')
mock_clean_up_caches.side_effect = [exc]
self.assertRaises(exception.InstanceDeployFailure,
pxe._fetch_images,
None,
mock_cache,
[('uuid', 'path')])
mock_clean_up_caches.assert_called_once_with(None, 'master_dir',
[('uuid', 'path')])
class PXEDriverTestCase(db_base.DbTestCase):
@ -622,8 +469,8 @@ class PXEDriverTestCase(db_base.DbTestCase):
task.node, None)
@mock.patch.object(deploy_utils, 'get_image_mb')
@mock.patch.object(pxe, '_get_image_file_path')
@mock.patch.object(pxe, '_cache_instance_image')
@mock.patch.object(iscsi_deploy, '_get_image_file_path')
@mock.patch.object(iscsi_deploy, 'cache_instance_image')
@mock.patch.object(neutron, 'update_neutron')
@mock.patch.object(manager_utils, 'node_power_action')
@mock.patch.object(manager_utils, 'node_set_boot_device')
@ -655,8 +502,8 @@ class PXEDriverTestCase(db_base.DbTestCase):
self.assertEqual(self.context.auth_token, token)
@mock.patch.object(deploy_utils, 'get_image_mb')
@mock.patch.object(pxe, '_get_image_file_path')
@mock.patch.object(pxe, '_cache_instance_image')
@mock.patch.object(iscsi_deploy, '_get_image_file_path')
@mock.patch.object(iscsi_deploy, 'cache_instance_image')
def test_deploy_image_too_large(self, mock_cache_instance_image,
mock_get_image_file_path,
mock_get_image_mb):
@ -690,15 +537,20 @@ class PXEDriverTestCase(db_base.DbTestCase):
update_neutron_mock.assert_called_once_with(
task, dhcp_opts)
@mock.patch.object(pxe, 'InstanceImageCache')
def test_continue_deploy_good(self, mock_image_cache):
@mock.patch.object(deploy_utils, 'notify_deploy_complete')
@mock.patch.object(deploy_utils, 'switch_pxe_config')
@mock.patch.object(iscsi_deploy, 'InstanceImageCache')
def test_continue_deploy_good(self, mock_image_cache, mock_switch_config,
notify_mock):
token_path = self._create_token_file()
self.node.power_state = states.POWER_ON
self.node.provision_state = states.DEPLOYWAIT
self.node.save()
root_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
def fake_deploy(**kwargs):
pass
return root_uuid
self.useFixture(fixtures.MonkeyPatch(
'ironic.drivers.modules.deploy_utils.deploy',
@ -715,8 +567,11 @@ class PXEDriverTestCase(db_base.DbTestCase):
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)
mock_switch_config.assert_called_once_with(pxe_config_path, root_uuid)
notify_mock.assert_called_once_with('123456')
@mock.patch.object(pxe, 'InstanceImageCache')
@mock.patch.object(iscsi_deploy, 'InstanceImageCache')
def test_continue_deploy_fail(self, mock_image_cache):
token_path = self._create_token_file()
self.node.power_state = states.POWER_ON
@ -742,7 +597,7 @@ class PXEDriverTestCase(db_base.DbTestCase):
mock_image_cache.assert_called_once_with()
mock_image_cache.return_value.clean_up.assert_called_once_with()
@mock.patch.object(pxe, 'InstanceImageCache')
@mock.patch.object(iscsi_deploy, 'InstanceImageCache')
def test_continue_deploy_ramdisk_fails(self, mock_image_cache):
token_path = self._create_token_file()
self.node.power_state = states.POWER_ON
@ -867,6 +722,7 @@ class PXEDriverTestCase(db_base.DbTestCase):
assert_false_path = [config_path, deploy_kernel_path, image_path,
pxe_mac_path, image_dir, instance_dir,
token_path]
for path in assert_false_path:
self.assertFalse(os.path.exists(path))
@ -882,8 +738,8 @@ class PXEDriverTestCase(db_base.DbTestCase):
master_d_kernel_path = os.path.join(CONF.pxe.tftp_master_path,
'deploy_kernel_uuid')
master_instance_path = os.path.join(CONF.pxe.instance_master_path,
'image_uuid')
instance_master_path = CONF.pxe.instance_master_path
master_instance_path = os.path.join(instance_master_path, 'image_uuid')
self.assertTrue(os.path.exists(master_d_kernel_path))
self.assertTrue(os.path.exists(master_instance_path))

View File

@ -41,8 +41,8 @@ class TestPXEUtils(db_base.DbTestCase):
'deployment_key': '0123456789ABCDEFGHIJKLMNOPQRSTUV',
'ari_path': u'/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/'
u'ramdisk',
'deployment_iscsi_iqn': u'iqn-1be26c0b-03f2-4d2e-ae87-c02d7f33'
u'c123',
'iscsi_target_iqn': u'iqn-1be26c0b-03f2-4d2e-ae87-c02d7f33'
u'c123',
'deployment_ari_path': u'/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7'
u'f33c123/deploy_ramdisk',
'pxe_append_params': 'test_param',
@ -51,7 +51,8 @@ class TestPXEUtils(db_base.DbTestCase):
'deployment_id': u'1be26c0b-03f2-4d2e-ae87-c02d7f33c123',
'ironic_api_url': 'http://192.168.122.184:6385',
'deployment_aki_path': u'/tftpboot/1be26c0b-03f2-4d2e-ae87-'
u'c02d7f33c123/deploy_kernel'
u'c02d7f33c123/deploy_kernel',
'disk': 'cciss/c0d0,sda,hda,vda'
}
self.node = object_utils.create_test_node(self.context)