Merge "Direct deploy serve HTTP images from conductor"
This commit is contained in:
commit
36e87dc5b4
@ -560,6 +560,8 @@ IRONIC_ANSIBLE_SSH_USER=${IRONIC_ANSIBLE_SSH_USER:-}
|
|||||||
# DevStack deployment, as we do not distribute this generated key to subnodes yet.
|
# DevStack deployment, as we do not distribute this generated key to subnodes yet.
|
||||||
IRONIC_ANSIBLE_SSH_KEY=${IRONIC_ANSIBLE_SSH_KEY:-$IRONIC_DATA_DIR/ansible_ssh_key}
|
IRONIC_ANSIBLE_SSH_KEY=${IRONIC_ANSIBLE_SSH_KEY:-$IRONIC_DATA_DIR/ansible_ssh_key}
|
||||||
|
|
||||||
|
IRONIC_AGENT_IMAGE_DOWNLOAD_SOURCE=${IRONIC_AGENT_IMAGE_DOWNLOAD_SOURCE:-swift}
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
# ---------
|
# ---------
|
||||||
|
|
||||||
@ -1105,6 +1107,8 @@ function configure_ironic {
|
|||||||
iniset $IRONIC_CONF_FILE agent deploy_logs_collect $IRONIC_DEPLOY_LOGS_COLLECT
|
iniset $IRONIC_CONF_FILE agent deploy_logs_collect $IRONIC_DEPLOY_LOGS_COLLECT
|
||||||
iniset $IRONIC_CONF_FILE agent deploy_logs_storage_backend $IRONIC_DEPLOY_LOGS_STORAGE_BACKEND
|
iniset $IRONIC_CONF_FILE agent deploy_logs_storage_backend $IRONIC_DEPLOY_LOGS_STORAGE_BACKEND
|
||||||
iniset $IRONIC_CONF_FILE agent deploy_logs_local_path $IRONIC_DEPLOY_LOGS_LOCAL_PATH
|
iniset $IRONIC_CONF_FILE agent deploy_logs_local_path $IRONIC_DEPLOY_LOGS_LOCAL_PATH
|
||||||
|
# Set image_download_source for direct interface
|
||||||
|
iniset $IRONIC_CONF_FILE agent image_download_source $IRONIC_AGENT_IMAGE_DOWNLOAD_SOURCE
|
||||||
|
|
||||||
# Configure Ironic conductor, if it was enabled.
|
# Configure Ironic conductor, if it was enabled.
|
||||||
if is_service_enabled ir-cond; then
|
if is_service_enabled ir-cond; then
|
||||||
|
@ -89,6 +89,19 @@ opts = [
|
|||||||
'forever or until manually deleted. Used when the '
|
'forever or until manually deleted. Used when the '
|
||||||
'deploy_logs_storage_backend is configured to '
|
'deploy_logs_storage_backend is configured to '
|
||||||
'"swift".')),
|
'"swift".')),
|
||||||
|
cfg.StrOpt('image_download_source',
|
||||||
|
choices=[('swift', _('IPA ramdisk retrieves instance image '
|
||||||
|
'from the Object Storage service.')),
|
||||||
|
('http', _('IPA ramdisk retrieves instance image '
|
||||||
|
'from HTTP service served at conductor '
|
||||||
|
'nodes.'))],
|
||||||
|
default='swift',
|
||||||
|
help=_('Specifies whether direct deploy interface should try '
|
||||||
|
'to use the image source directly or if ironic should '
|
||||||
|
'cache the image on the conductor and serve it from '
|
||||||
|
'ironic\'s own http server. This option takes effect '
|
||||||
|
'only when instance image is provided from the Image '
|
||||||
|
'service.')),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -96,6 +96,13 @@ opts = [
|
|||||||
help=_('Whether to upload the config drive to object store. '
|
help=_('Whether to upload the config drive to object store. '
|
||||||
'Set this option to True to store config drive '
|
'Set this option to True to store config drive '
|
||||||
'in a swift endpoint.')),
|
'in a swift endpoint.')),
|
||||||
|
cfg.StrOpt('http_image_subdir',
|
||||||
|
default='agent_images',
|
||||||
|
help=_('The name of subdirectory under ironic-conductor '
|
||||||
|
'node\'s HTTP root path which is used to place instance '
|
||||||
|
'images for the direct deploy interface, when local '
|
||||||
|
'HTTP service is incorporated to provide instance image '
|
||||||
|
'instead of swift tempurls.')),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -145,6 +145,27 @@ def validate_image_proxies(node):
|
|||||||
raise exception.InvalidParameterValue(msg)
|
raise exception.InvalidParameterValue(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_http_provisioning_configuration(node):
|
||||||
|
"""Validate configuration options required to perform HTTP provisioning.
|
||||||
|
|
||||||
|
:param node: an ironic node object
|
||||||
|
:raises: MissingParameterValue if required option(s) is not set.
|
||||||
|
"""
|
||||||
|
image_source = node.instance_info.get('image_source')
|
||||||
|
if (not service_utils.is_glance_image(image_source) or
|
||||||
|
CONF.agent.image_download_source != 'http'):
|
||||||
|
return
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'[deploy]http_url': CONF.deploy.http_url,
|
||||||
|
'[deploy]http_root': CONF.deploy.http_root,
|
||||||
|
'[deploy]http_image_subdir': CONF.deploy.http_image_subdir
|
||||||
|
}
|
||||||
|
error_msg = _('Node %s failed to validate http provisoning. Some '
|
||||||
|
'configuration options were missing') % node.uuid
|
||||||
|
deploy_utils.check_for_missing_params(params, error_msg)
|
||||||
|
|
||||||
|
|
||||||
class AgentDeployMixin(agent_base_vendor.AgentDeployMixin):
|
class AgentDeployMixin(agent_base_vendor.AgentDeployMixin):
|
||||||
|
|
||||||
@METRICS.timer('AgentDeployMixin.deploy_has_started')
|
@METRICS.timer('AgentDeployMixin.deploy_has_started')
|
||||||
@ -338,6 +359,10 @@ class AgentDeployMixin(agent_base_vendor.AgentDeployMixin):
|
|||||||
else:
|
else:
|
||||||
manager_utils.node_set_boot_device(task, 'disk', persistent=True)
|
manager_utils.node_set_boot_device(task, 'disk', persistent=True)
|
||||||
|
|
||||||
|
# Remove symbolic link when deploy is done.
|
||||||
|
if CONF.agent.image_download_source == 'http':
|
||||||
|
deploy_utils.remove_http_instance_symlink(task.node.uuid)
|
||||||
|
|
||||||
LOG.debug('Rebooting node %s to instance', node.uuid)
|
LOG.debug('Rebooting node %s to instance', node.uuid)
|
||||||
self.reboot_and_finish_deploy(task)
|
self.reboot_and_finish_deploy(task)
|
||||||
|
|
||||||
@ -397,6 +422,8 @@ class AgentDeploy(AgentDeployMixin, base.DeployInterface):
|
|||||||
"image_source's image_checksum must be provided in "
|
"image_source's image_checksum must be provided in "
|
||||||
"instance_info for node %s") % node.uuid)
|
"instance_info for node %s") % node.uuid)
|
||||||
|
|
||||||
|
validate_http_provisioning_configuration(node)
|
||||||
|
|
||||||
check_image_size(task, image_source)
|
check_image_size(task, image_source)
|
||||||
# Validate the root device hints
|
# Validate the root device hints
|
||||||
try:
|
try:
|
||||||
@ -562,6 +589,8 @@ class AgentDeploy(AgentDeployMixin, base.DeployInterface):
|
|||||||
task.driver.boot.clean_up_instance(task)
|
task.driver.boot.clean_up_instance(task)
|
||||||
provider = dhcp_factory.DHCPFactory()
|
provider = dhcp_factory.DHCPFactory()
|
||||||
provider.clean_dhcp(task)
|
provider.clean_dhcp(task)
|
||||||
|
if CONF.agent.image_download_source == 'http':
|
||||||
|
deploy_utils.destroy_http_instance_images(task.node)
|
||||||
|
|
||||||
def take_over(self, task):
|
def take_over(self, task):
|
||||||
"""Take over management of this node from a dead conductor.
|
"""Take over management of this node from a dead conductor.
|
||||||
|
@ -22,9 +22,11 @@ import time
|
|||||||
|
|
||||||
from ironic_lib import disk_utils
|
from ironic_lib import disk_utils
|
||||||
from ironic_lib import metrics_utils
|
from ironic_lib import metrics_utils
|
||||||
|
from ironic_lib import utils as il_utils
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import excutils
|
from oslo_utils import excutils
|
||||||
|
from oslo_utils import fileutils
|
||||||
from oslo_utils import netutils
|
from oslo_utils import netutils
|
||||||
from oslo_utils import strutils
|
from oslo_utils import strutils
|
||||||
import six
|
import six
|
||||||
@ -1069,6 +1071,98 @@ def _check_disk_layout_unchanged(node, i_info):
|
|||||||
{'error_msg': error_msg})
|
{'error_msg': error_msg})
|
||||||
|
|
||||||
|
|
||||||
|
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_http_image_symlink_dir_path():
|
||||||
|
"""Generate the dir for storing symlinks to cached instance images."""
|
||||||
|
return os.path.join(CONF.deploy.http_root, CONF.deploy.http_image_subdir)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_http_image_symlink_file_path(node_uuid):
|
||||||
|
"""Generate the full path for the symlink to an cached instance image."""
|
||||||
|
return os.path.join(_get_http_image_symlink_dir_path(), node_uuid)
|
||||||
|
|
||||||
|
|
||||||
|
def direct_deploy_should_convert_raw_image(node):
|
||||||
|
"""Whether converts image to raw format for specified node.
|
||||||
|
|
||||||
|
:param node: ironic node object
|
||||||
|
:returns: Boolean, whether the direct deploy interface should convert
|
||||||
|
image to raw.
|
||||||
|
"""
|
||||||
|
iwdi = node.driver_internal_info.get('is_whole_disk_image')
|
||||||
|
return CONF.force_raw_images and CONF.agent.stream_raw_images and iwdi
|
||||||
|
|
||||||
|
|
||||||
|
@image_cache.cleanup(priority=50)
|
||||||
|
class InstanceImageCache(image_cache.ImageCache):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
@METRICS.timer('cache_instance_image')
|
||||||
|
def cache_instance_image(ctx, node, force_raw=CONF.force_raw_images):
|
||||||
|
"""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
|
||||||
|
:param force_raw: whether convert image to raw format
|
||||||
|
: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 %(image)s for node %(uuid)s",
|
||||||
|
{'image': uuid, 'uuid': node.uuid})
|
||||||
|
|
||||||
|
fetch_images(ctx, InstanceImageCache(), [(uuid, image_path)],
|
||||||
|
force_raw)
|
||||||
|
|
||||||
|
return (uuid, image_path)
|
||||||
|
|
||||||
|
|
||||||
|
@METRICS.timer('destroy_images')
|
||||||
|
def destroy_images(node_uuid):
|
||||||
|
"""Delete instance's image file.
|
||||||
|
|
||||||
|
:param node_uuid: the uuid of the ironic node.
|
||||||
|
"""
|
||||||
|
il_utils.unlink_without_raise(_get_image_file_path(node_uuid))
|
||||||
|
utils.rmtree_without_raise(_get_image_dir_path(node_uuid))
|
||||||
|
InstanceImageCache().clean_up()
|
||||||
|
|
||||||
|
|
||||||
|
def remove_http_instance_symlink(node_uuid):
|
||||||
|
symlink_path = _get_http_image_symlink_file_path(node_uuid)
|
||||||
|
il_utils.unlink_without_raise(symlink_path)
|
||||||
|
|
||||||
|
|
||||||
|
def destroy_http_instance_images(node):
|
||||||
|
"""Delete instance image file and symbolic link refers to it."""
|
||||||
|
remove_http_instance_symlink(node.uuid)
|
||||||
|
destroy_images(node.uuid)
|
||||||
|
|
||||||
|
|
||||||
@METRICS.timer('build_instance_info_for_deploy')
|
@METRICS.timer('build_instance_info_for_deploy')
|
||||||
def build_instance_info_for_deploy(task):
|
def build_instance_info_for_deploy(task):
|
||||||
"""Build instance_info necessary for deploying to a node.
|
"""Build instance_info necessary for deploying to a node.
|
||||||
@ -1100,17 +1194,55 @@ def build_instance_info_for_deploy(task):
|
|||||||
instance_info = node.instance_info
|
instance_info = node.instance_info
|
||||||
iwdi = node.driver_internal_info.get('is_whole_disk_image')
|
iwdi = node.driver_internal_info.get('is_whole_disk_image')
|
||||||
image_source = instance_info['image_source']
|
image_source = instance_info['image_source']
|
||||||
|
|
||||||
if service_utils.is_glance_image(image_source):
|
if service_utils.is_glance_image(image_source):
|
||||||
glance = image_service.GlanceImageService(version=2,
|
glance = image_service.GlanceImageService(version=2,
|
||||||
context=task.context)
|
context=task.context)
|
||||||
image_info = glance.show(image_source)
|
image_info = glance.show(image_source)
|
||||||
LOG.debug('Got image info: %(info)s for node %(node)s.',
|
LOG.debug('Got image info: %(info)s for node %(node)s.',
|
||||||
{'info': image_info, 'node': node.uuid})
|
{'info': image_info, 'node': node.uuid})
|
||||||
swift_temp_url = glance.swift_temp_url(image_info)
|
if CONF.agent.image_download_source == 'swift':
|
||||||
validate_image_url(swift_temp_url, secret=True)
|
swift_temp_url = glance.swift_temp_url(image_info)
|
||||||
instance_info['image_url'] = swift_temp_url
|
validate_image_url(swift_temp_url, secret=True)
|
||||||
instance_info['image_checksum'] = image_info['checksum']
|
instance_info['image_url'] = swift_temp_url
|
||||||
instance_info['image_disk_format'] = image_info['disk_format']
|
instance_info['image_checksum'] = image_info['checksum']
|
||||||
|
instance_info['image_disk_format'] = image_info['disk_format']
|
||||||
|
else:
|
||||||
|
# Ironic cache and serve images from httpboot server
|
||||||
|
force_raw = direct_deploy_should_convert_raw_image(node)
|
||||||
|
_, image_path = cache_instance_image(task.context, node,
|
||||||
|
force_raw=force_raw)
|
||||||
|
if force_raw:
|
||||||
|
time_start = time.time()
|
||||||
|
LOG.debug('Start calculating checksum for image %(image)s.',
|
||||||
|
{'image': image_path})
|
||||||
|
checksum = fileutils.compute_file_checksum(image_path,
|
||||||
|
algorithm='md5')
|
||||||
|
time_elapsed = time.time() - time_start
|
||||||
|
LOG.debug('Recalculated checksum for image %(image)s in '
|
||||||
|
'%(delta).2f seconds, new checksum %(checksum)s ',
|
||||||
|
{'image': image_path, 'delta': time_elapsed,
|
||||||
|
'checksum': checksum})
|
||||||
|
instance_info['image_checksum'] = checksum
|
||||||
|
instance_info['image_disk_format'] = 'raw'
|
||||||
|
else:
|
||||||
|
instance_info['image_checksum'] = image_info['checksum']
|
||||||
|
instance_info['image_disk_format'] = image_info['disk_format']
|
||||||
|
|
||||||
|
# Create symlink and update image url
|
||||||
|
symlink_dir = _get_http_image_symlink_dir_path()
|
||||||
|
fileutils.ensure_tree(symlink_dir)
|
||||||
|
symlink_path = _get_http_image_symlink_file_path(node.uuid)
|
||||||
|
utils.create_link_without_raise(image_path, symlink_path)
|
||||||
|
base_url = CONF.deploy.http_url
|
||||||
|
if base_url.endswith('/'):
|
||||||
|
base_url = base_url[:-1]
|
||||||
|
http_image_url = '/'.join(
|
||||||
|
[base_url, CONF.deploy.http_image_subdir,
|
||||||
|
node.uuid])
|
||||||
|
validate_image_url(http_image_url, secret=True)
|
||||||
|
instance_info['image_url'] = http_image_url
|
||||||
|
|
||||||
instance_info['image_container_format'] = (
|
instance_info['image_container_format'] = (
|
||||||
image_info['container_format'])
|
image_info['container_format'])
|
||||||
instance_info['image_tags'] = image_info.get('tags', [])
|
instance_info['image_tags'] = image_info.get('tags', [])
|
||||||
|
@ -99,6 +99,11 @@ class ImageCache(object):
|
|||||||
href_encoded = href.encode('utf-8') if six.PY2 else href
|
href_encoded = href.encode('utf-8') if six.PY2 else href
|
||||||
master_file_name = str(uuid.uuid5(uuid.NAMESPACE_URL,
|
master_file_name = str(uuid.uuid5(uuid.NAMESPACE_URL,
|
||||||
href_encoded))
|
href_encoded))
|
||||||
|
# NOTE(kaifeng) The ".converted" suffix acts as an indicator that the
|
||||||
|
# image cached has gone through the conversion logic.
|
||||||
|
if force_raw:
|
||||||
|
master_file_name = master_file_name + '.converted'
|
||||||
|
|
||||||
master_path = os.path.join(self.master_dir, master_file_name)
|
master_path = os.path.join(self.master_dir, master_file_name)
|
||||||
|
|
||||||
if CONF.parallel_image_downloads:
|
if CONF.parallel_image_downloads:
|
||||||
|
@ -13,21 +13,17 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from ironic_lib import disk_utils
|
from ironic_lib import disk_utils
|
||||||
from ironic_lib import metrics_utils
|
from ironic_lib import metrics_utils
|
||||||
from ironic_lib import utils as il_utils
|
from ironic_lib import utils as il_utils
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import excutils
|
from oslo_utils import excutils
|
||||||
from oslo_utils import fileutils
|
|
||||||
from six.moves.urllib import parse
|
from six.moves.urllib import parse
|
||||||
|
|
||||||
from ironic.common import dhcp_factory
|
from ironic.common import dhcp_factory
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
from ironic.common.i18n import _
|
from ironic.common.i18n import _
|
||||||
from ironic.common import states
|
from ironic.common import states
|
||||||
from ironic.common import utils
|
|
||||||
from ironic.conductor import task_manager
|
from ironic.conductor import task_manager
|
||||||
from ironic.conductor import utils as manager_utils
|
from ironic.conductor import utils as manager_utils
|
||||||
from ironic.conf import CONF
|
from ironic.conf import CONF
|
||||||
@ -35,7 +31,6 @@ from ironic.drivers import base
|
|||||||
from ironic.drivers.modules import agent_base_vendor
|
from ironic.drivers.modules import agent_base_vendor
|
||||||
from ironic.drivers.modules import boot_mode_utils
|
from ironic.drivers.modules import boot_mode_utils
|
||||||
from ironic.drivers.modules import deploy_utils
|
from ironic.drivers.modules import deploy_utils
|
||||||
from ironic.drivers.modules import image_cache
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -44,28 +39,6 @@ METRICS = metrics_utils.get_metrics_logger(__name__)
|
|||||||
DISK_LAYOUT_PARAMS = ('root_gb', 'swap_mb', 'ephemeral_gb')
|
DISK_LAYOUT_PARAMS = ('root_gb', 'swap_mb', 'ephemeral_gb')
|
||||||
|
|
||||||
|
|
||||||
@image_cache.cleanup(priority=50)
|
|
||||||
class InstanceImageCache(image_cache.ImageCache):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
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 _save_disk_layout(node, i_info):
|
def _save_disk_layout(node, i_info):
|
||||||
"""Saves the disk layout.
|
"""Saves the disk layout.
|
||||||
|
|
||||||
@ -101,7 +74,7 @@ def check_image_size(task):
|
|||||||
return
|
return
|
||||||
|
|
||||||
i_info = deploy_utils.parse_instance_info(task.node)
|
i_info = deploy_utils.parse_instance_info(task.node)
|
||||||
image_path = _get_image_file_path(task.node.uuid)
|
image_path = deploy_utils._get_image_file_path(task.node.uuid)
|
||||||
image_mb = disk_utils.get_image_mb(image_path)
|
image_mb = disk_utils.get_image_mb(image_path)
|
||||||
root_mb = 1024 * int(i_info['root_gb'])
|
root_mb = 1024 * int(i_info['root_gb'])
|
||||||
if image_mb > root_mb:
|
if image_mb > root_mb:
|
||||||
@ -111,43 +84,6 @@ def check_image_size(task):
|
|||||||
raise exception.InstanceDeployFailure(msg)
|
raise exception.InstanceDeployFailure(msg)
|
||||||
|
|
||||||
|
|
||||||
@METRICS.timer('cache_instance_image')
|
|
||||||
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 = deploy_utils.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)],
|
|
||||||
CONF.force_raw_images)
|
|
||||||
|
|
||||||
return (uuid, image_path)
|
|
||||||
|
|
||||||
|
|
||||||
@METRICS.timer('destroy_images')
|
|
||||||
def destroy_images(node_uuid):
|
|
||||||
"""Delete instance's image file.
|
|
||||||
|
|
||||||
:param node_uuid: the uuid of the ironic node.
|
|
||||||
"""
|
|
||||||
il_utils.unlink_without_raise(_get_image_file_path(node_uuid))
|
|
||||||
utils.rmtree_without_raise(_get_image_dir_path(node_uuid))
|
|
||||||
InstanceImageCache().clean_up()
|
|
||||||
|
|
||||||
|
|
||||||
@METRICS.timer('get_deploy_info')
|
@METRICS.timer('get_deploy_info')
|
||||||
def get_deploy_info(node, address, iqn, port=None, lun='1'):
|
def get_deploy_info(node, address, iqn, port=None, lun='1'):
|
||||||
"""Returns the information required for doing iSCSI deploy in a dictionary.
|
"""Returns the information required for doing iSCSI deploy in a dictionary.
|
||||||
@ -169,7 +105,7 @@ def get_deploy_info(node, address, iqn, port=None, lun='1'):
|
|||||||
'port': port or CONF.iscsi.portal_port,
|
'port': port or CONF.iscsi.portal_port,
|
||||||
'iqn': iqn,
|
'iqn': iqn,
|
||||||
'lun': lun,
|
'lun': lun,
|
||||||
'image_path': _get_image_file_path(node.uuid),
|
'image_path': deploy_utils._get_image_file_path(node.uuid),
|
||||||
'node_uuid': node.uuid}
|
'node_uuid': node.uuid}
|
||||||
|
|
||||||
is_whole_disk_image = node.driver_internal_info['is_whole_disk_image']
|
is_whole_disk_image = node.driver_internal_info['is_whole_disk_image']
|
||||||
@ -241,7 +177,7 @@ def continue_deploy(task, **kwargs):
|
|||||||
'Error: %(error)s') %
|
'Error: %(error)s') %
|
||||||
{'instance': node.instance_uuid, 'error': msg})
|
{'instance': node.instance_uuid, 'error': msg})
|
||||||
deploy_utils.set_failed_state(task, msg)
|
deploy_utils.set_failed_state(task, msg)
|
||||||
destroy_images(task.node.uuid)
|
deploy_utils.destroy_images(task.node.uuid)
|
||||||
if raise_exception:
|
if raise_exception:
|
||||||
raise exception.InstanceDeployFailure(msg)
|
raise exception.InstanceDeployFailure(msg)
|
||||||
|
|
||||||
@ -288,7 +224,7 @@ def continue_deploy(task, **kwargs):
|
|||||||
# for any future rebuilds
|
# for any future rebuilds
|
||||||
_save_disk_layout(node, deploy_utils.parse_instance_info(node))
|
_save_disk_layout(node, deploy_utils.parse_instance_info(node))
|
||||||
|
|
||||||
destroy_images(node.uuid)
|
deploy_utils.destroy_images(node.uuid)
|
||||||
return uuid_dict_returned
|
return uuid_dict_returned
|
||||||
|
|
||||||
|
|
||||||
@ -475,7 +411,7 @@ class ISCSIDeploy(AgentDeployMixin, base.DeployInterface):
|
|||||||
"""
|
"""
|
||||||
node = task.node
|
node = task.node
|
||||||
if task.driver.storage.should_write_image(task):
|
if task.driver.storage.should_write_image(task):
|
||||||
cache_instance_image(task.context, node)
|
deploy_utils.cache_instance_image(task.context, node)
|
||||||
check_image_size(task)
|
check_image_size(task)
|
||||||
manager_utils.node_power_action(task, states.REBOOT)
|
manager_utils.node_power_action(task, states.REBOOT)
|
||||||
|
|
||||||
@ -572,7 +508,7 @@ class ISCSIDeploy(AgentDeployMixin, base.DeployInterface):
|
|||||||
|
|
||||||
:param task: a TaskManager instance containing the node to act on.
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
"""
|
"""
|
||||||
destroy_images(task.node.uuid)
|
deploy_utils.destroy_images(task.node.uuid)
|
||||||
task.driver.boot.clean_up_ramdisk(task)
|
task.driver.boot.clean_up_ramdisk(task)
|
||||||
task.driver.boot.clean_up_instance(task)
|
task.driver.boot.clean_up_instance(task)
|
||||||
provider = dhcp_factory.DHCPFactory()
|
provider = dhcp_factory.DHCPFactory()
|
||||||
|
@ -138,6 +138,30 @@ class TestAgentMethods(db_base.DbTestCase):
|
|||||||
task, 'fake-image')
|
task, 'fake-image')
|
||||||
show_mock.assert_called_once_with(self.context, 'fake-image')
|
show_mock.assert_called_once_with(self.context, 'fake-image')
|
||||||
|
|
||||||
|
@mock.patch.object(deploy_utils, 'check_for_missing_params')
|
||||||
|
def test_validate_http_provisioning_not_glance(self, utils_mock):
|
||||||
|
agent.validate_http_provisioning_configuration(self.node)
|
||||||
|
utils_mock.assert_not_called()
|
||||||
|
|
||||||
|
@mock.patch.object(deploy_utils, 'check_for_missing_params')
|
||||||
|
def test_validate_http_provisioning_not_http(self, utils_mock):
|
||||||
|
i_info = self.node.instance_info
|
||||||
|
i_info['image_source'] = '0448fa34-4db1-407b-a051-6357d5f86c59'
|
||||||
|
self.node.instance_info = i_info
|
||||||
|
agent.validate_http_provisioning_configuration(self.node)
|
||||||
|
utils_mock.assert_not_called()
|
||||||
|
|
||||||
|
def test_validate_http_provisioning_missing_args(self):
|
||||||
|
CONF.set_override('image_download_source', 'http', group='agent')
|
||||||
|
CONF.set_override('http_url', None, group='deploy')
|
||||||
|
i_info = self.node.instance_info
|
||||||
|
i_info['image_source'] = '0448fa34-4db1-407b-a051-6357d5f86c59'
|
||||||
|
self.node.instance_info = i_info
|
||||||
|
self.assertRaisesRegex(exception.MissingParameterValue,
|
||||||
|
'failed to validate http provisoning',
|
||||||
|
agent.validate_http_provisioning_configuration,
|
||||||
|
self.node)
|
||||||
|
|
||||||
|
|
||||||
class TestAgentDeploy(db_base.DbTestCase):
|
class TestAgentDeploy(db_base.DbTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -164,12 +188,14 @@ class TestAgentDeploy(db_base.DbTestCase):
|
|||||||
expected = agent.COMMON_PROPERTIES
|
expected = agent.COMMON_PROPERTIES
|
||||||
self.assertEqual(expected, self.driver.get_properties())
|
self.assertEqual(expected, self.driver.get_properties())
|
||||||
|
|
||||||
|
@mock.patch.object(agent, 'validate_http_provisioning_configuration',
|
||||||
|
autospec=True)
|
||||||
@mock.patch.object(deploy_utils, 'validate_capabilities',
|
@mock.patch.object(deploy_utils, 'validate_capabilities',
|
||||||
spec_set=True, autospec=True)
|
spec_set=True, autospec=True)
|
||||||
@mock.patch.object(images, 'image_show', autospec=True)
|
@mock.patch.object(images, 'image_show', autospec=True)
|
||||||
@mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
|
@mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
|
||||||
def test_validate(self, pxe_boot_validate_mock, show_mock,
|
def test_validate(self, pxe_boot_validate_mock, show_mock,
|
||||||
validate_capability_mock):
|
validate_capability_mock, validate_http_mock):
|
||||||
with task_manager.acquire(
|
with task_manager.acquire(
|
||||||
self.context, self.node['uuid'], shared=False) as task:
|
self.context, self.node['uuid'], shared=False) as task:
|
||||||
self.driver.validate(task)
|
self.driver.validate(task)
|
||||||
@ -177,14 +203,17 @@ class TestAgentDeploy(db_base.DbTestCase):
|
|||||||
task.driver.boot, task)
|
task.driver.boot, task)
|
||||||
show_mock.assert_called_once_with(self.context, 'fake-image')
|
show_mock.assert_called_once_with(self.context, 'fake-image')
|
||||||
validate_capability_mock.assert_called_once_with(task.node)
|
validate_capability_mock.assert_called_once_with(task.node)
|
||||||
|
validate_http_mock.assert_called_once_with(task.node)
|
||||||
|
|
||||||
|
@mock.patch.object(agent, 'validate_http_provisioning_configuration',
|
||||||
|
autospec=True)
|
||||||
@mock.patch.object(deploy_utils, 'validate_capabilities',
|
@mock.patch.object(deploy_utils, 'validate_capabilities',
|
||||||
spec_set=True, autospec=True)
|
spec_set=True, autospec=True)
|
||||||
@mock.patch.object(images, 'image_show', autospec=True)
|
@mock.patch.object(images, 'image_show', autospec=True)
|
||||||
@mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
|
@mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
|
||||||
def test_validate_driver_info_manage_agent_boot_false(
|
def test_validate_driver_info_manage_agent_boot_false(
|
||||||
self, pxe_boot_validate_mock, show_mock,
|
self, pxe_boot_validate_mock, show_mock,
|
||||||
validate_capability_mock):
|
validate_capability_mock, validate_http_mock):
|
||||||
|
|
||||||
self.config(manage_agent_boot=False, group='agent')
|
self.config(manage_agent_boot=False, group='agent')
|
||||||
self.node.driver_info = {}
|
self.node.driver_info = {}
|
||||||
@ -195,6 +224,7 @@ class TestAgentDeploy(db_base.DbTestCase):
|
|||||||
self.assertFalse(pxe_boot_validate_mock.called)
|
self.assertFalse(pxe_boot_validate_mock.called)
|
||||||
show_mock.assert_called_once_with(self.context, 'fake-image')
|
show_mock.assert_called_once_with(self.context, 'fake-image')
|
||||||
validate_capability_mock.assert_called_once_with(task.node)
|
validate_capability_mock.assert_called_once_with(task.node)
|
||||||
|
validate_http_mock.assert_called_once_with(task.node)
|
||||||
|
|
||||||
@mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
|
@mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
|
||||||
def test_validate_instance_info_missing_params(
|
def test_validate_instance_info_missing_params(
|
||||||
@ -226,10 +256,12 @@ class TestAgentDeploy(db_base.DbTestCase):
|
|||||||
pxe_boot_validate_mock.assert_called_once_with(
|
pxe_boot_validate_mock.assert_called_once_with(
|
||||||
task.driver.boot, task)
|
task.driver.boot, task)
|
||||||
|
|
||||||
|
@mock.patch.object(agent, 'validate_http_provisioning_configuration',
|
||||||
|
autospec=True)
|
||||||
@mock.patch.object(images, 'image_show', autospec=True)
|
@mock.patch.object(images, 'image_show', autospec=True)
|
||||||
@mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
|
@mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
|
||||||
def test_validate_invalid_root_device_hints(
|
def test_validate_invalid_root_device_hints(
|
||||||
self, pxe_boot_validate_mock, show_mock):
|
self, pxe_boot_validate_mock, show_mock, validate_http_mock):
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
shared=True) as task:
|
shared=True) as task:
|
||||||
task.node.properties['root_device'] = {'size': 'not-int'}
|
task.node.properties['root_device'] = {'size': 'not-int'}
|
||||||
@ -238,10 +270,14 @@ class TestAgentDeploy(db_base.DbTestCase):
|
|||||||
pxe_boot_validate_mock.assert_called_once_with(
|
pxe_boot_validate_mock.assert_called_once_with(
|
||||||
task.driver.boot, task)
|
task.driver.boot, task)
|
||||||
show_mock.assert_called_once_with(self.context, 'fake-image')
|
show_mock.assert_called_once_with(self.context, 'fake-image')
|
||||||
|
validate_http_mock.assert_called_once_with(task.node)
|
||||||
|
|
||||||
|
@mock.patch.object(agent, 'validate_http_provisioning_configuration',
|
||||||
|
autospec=True)
|
||||||
@mock.patch.object(images, 'image_show', autospec=True)
|
@mock.patch.object(images, 'image_show', autospec=True)
|
||||||
@mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
|
@mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
|
||||||
def test_validate_invalid_proxies(self, pxe_boot_validate_mock, show_mock):
|
def test_validate_invalid_proxies(self, pxe_boot_validate_mock, show_mock,
|
||||||
|
validate_http_mock):
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
shared=True) as task:
|
shared=True) as task:
|
||||||
task.node.driver_info.update({
|
task.node.driver_info.update({
|
||||||
@ -254,6 +290,7 @@ class TestAgentDeploy(db_base.DbTestCase):
|
|||||||
pxe_boot_validate_mock.assert_called_once_with(
|
pxe_boot_validate_mock.assert_called_once_with(
|
||||||
task.driver.boot, task)
|
task.driver.boot, task)
|
||||||
show_mock.assert_called_once_with(self.context, 'fake-image')
|
show_mock.assert_called_once_with(self.context, 'fake-image')
|
||||||
|
validate_http_mock.assert_called_once_with(task.node)
|
||||||
|
|
||||||
@mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
|
@mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
|
||||||
@mock.patch.object(deploy_utils, 'check_for_missing_params',
|
@mock.patch.object(deploy_utils, 'check_for_missing_params',
|
||||||
@ -948,6 +985,8 @@ class TestAgentDeploy(db_base.DbTestCase):
|
|||||||
self.assertEqual(states.ACTIVE,
|
self.assertEqual(states.ACTIVE,
|
||||||
task.node.target_provision_state)
|
task.node.target_provision_state)
|
||||||
|
|
||||||
|
@mock.patch.object(deploy_utils, 'remove_http_instance_symlink',
|
||||||
|
autospec=True)
|
||||||
@mock.patch.object(agent.LOG, 'warning', spec_set=True, autospec=True)
|
@mock.patch.object(agent.LOG, 'warning', spec_set=True, autospec=True)
|
||||||
@mock.patch.object(agent.AgentDeployMixin, '_get_uuid_from_result',
|
@mock.patch.object(agent.AgentDeployMixin, '_get_uuid_from_result',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@ -963,8 +1002,9 @@ class TestAgentDeploy(db_base.DbTestCase):
|
|||||||
def test_reboot_to_instance(self, check_deploy_mock,
|
def test_reboot_to_instance(self, check_deploy_mock,
|
||||||
prepare_instance_mock, power_off_mock,
|
prepare_instance_mock, power_off_mock,
|
||||||
get_power_state_mock, node_power_action_mock,
|
get_power_state_mock, node_power_action_mock,
|
||||||
uuid_mock, log_mock):
|
uuid_mock, log_mock, remove_symlink_mock):
|
||||||
self.config(manage_agent_boot=True, group='agent')
|
self.config(manage_agent_boot=True, group='agent')
|
||||||
|
self.config(image_download_source='http', group='agent')
|
||||||
check_deploy_mock.return_value = None
|
check_deploy_mock.return_value = None
|
||||||
uuid_mock.return_value = None
|
uuid_mock.return_value = None
|
||||||
self.node.provision_state = states.DEPLOYWAIT
|
self.node.provision_state = states.DEPLOYWAIT
|
||||||
@ -990,6 +1030,7 @@ class TestAgentDeploy(db_base.DbTestCase):
|
|||||||
task, states.POWER_ON)
|
task, states.POWER_ON)
|
||||||
self.assertEqual(states.ACTIVE, task.node.provision_state)
|
self.assertEqual(states.ACTIVE, task.node.provision_state)
|
||||||
self.assertEqual(states.NOSTATE, task.node.target_provision_state)
|
self.assertEqual(states.NOSTATE, task.node.target_provision_state)
|
||||||
|
self.assertTrue(remove_symlink_mock.called)
|
||||||
|
|
||||||
@mock.patch.object(agent.LOG, 'warning', spec_set=True, autospec=True)
|
@mock.patch.object(agent.LOG, 'warning', spec_set=True, autospec=True)
|
||||||
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
|
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
|
||||||
|
@ -19,10 +19,12 @@ import tempfile
|
|||||||
import time
|
import time
|
||||||
import types
|
import types
|
||||||
|
|
||||||
|
import fixtures
|
||||||
from ironic_lib import disk_utils
|
from ironic_lib import disk_utils
|
||||||
import mock
|
import mock
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from oslo_utils import fileutils
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
import testtools
|
import testtools
|
||||||
from testtools import matchers
|
from testtools import matchers
|
||||||
@ -1718,6 +1720,42 @@ class AgentMethodsTestCase(db_base.DbTestCase):
|
|||||||
self.assertEqual('https://api-url', options['ipa-api-url'])
|
self.assertEqual('https://api-url', options['ipa-api-url'])
|
||||||
self.assertEqual(0, options['coreos.configdrive'])
|
self.assertEqual(0, options['coreos.configdrive'])
|
||||||
|
|
||||||
|
def test_direct_deploy_should_convert_raw_image_true(self):
|
||||||
|
cfg.CONF.set_override('force_raw_images', True)
|
||||||
|
cfg.CONF.set_override('stream_raw_images', True, group='agent')
|
||||||
|
internal_info = self.node.driver_internal_info
|
||||||
|
internal_info['is_whole_disk_image'] = True
|
||||||
|
self.node.driver_internal_info = internal_info
|
||||||
|
self.assertTrue(
|
||||||
|
utils.direct_deploy_should_convert_raw_image(self.node))
|
||||||
|
|
||||||
|
def test_direct_deploy_should_convert_raw_image_no_force_raw(self):
|
||||||
|
cfg.CONF.set_override('force_raw_images', False)
|
||||||
|
cfg.CONF.set_override('stream_raw_images', True, group='agent')
|
||||||
|
internal_info = self.node.driver_internal_info
|
||||||
|
internal_info['is_whole_disk_image'] = True
|
||||||
|
self.node.driver_internal_info = internal_info
|
||||||
|
self.assertFalse(
|
||||||
|
utils.direct_deploy_should_convert_raw_image(self.node))
|
||||||
|
|
||||||
|
def test_direct_deploy_should_convert_raw_image_no_stream(self):
|
||||||
|
cfg.CONF.set_override('force_raw_images', True)
|
||||||
|
cfg.CONF.set_override('stream_raw_images', False, group='agent')
|
||||||
|
internal_info = self.node.driver_internal_info
|
||||||
|
internal_info['is_whole_disk_image'] = True
|
||||||
|
self.node.driver_internal_info = internal_info
|
||||||
|
self.assertFalse(
|
||||||
|
utils.direct_deploy_should_convert_raw_image(self.node))
|
||||||
|
|
||||||
|
def test_direct_deploy_should_convert_raw_image_partition(self):
|
||||||
|
cfg.CONF.set_override('force_raw_images', True)
|
||||||
|
cfg.CONF.set_override('stream_raw_images', True, group='agent')
|
||||||
|
internal_info = self.node.driver_internal_info
|
||||||
|
internal_info['is_whole_disk_image'] = False
|
||||||
|
self.node.driver_internal_info = internal_info
|
||||||
|
self.assertFalse(
|
||||||
|
utils.direct_deploy_should_convert_raw_image(self.node))
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(disk_utils, 'is_block_device', autospec=True)
|
@mock.patch.object(disk_utils, 'is_block_device', autospec=True)
|
||||||
@mock.patch.object(utils, 'login_iscsi', lambda *_: None)
|
@mock.patch.object(utils, 'login_iscsi', lambda *_: None)
|
||||||
@ -2383,6 +2421,118 @@ class TestBuildInstanceInfoForDeploy(db_base.DbTestCase):
|
|||||||
utils.build_instance_info_for_deploy, task)
|
utils.build_instance_info_for_deploy, task)
|
||||||
|
|
||||||
|
|
||||||
|
class TestBuildInstanceInfoForHttpProvisioning(db_base.DbTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestBuildInstanceInfoForHttpProvisioning, self).setUp()
|
||||||
|
self.node = obj_utils.create_test_node(self.context,
|
||||||
|
boot_interface='pxe',
|
||||||
|
deploy_interface='direct')
|
||||||
|
i_info = self.node.instance_info
|
||||||
|
i_info['image_source'] = '733d1c44-a2ea-414b-aca7-69decf20d810'
|
||||||
|
i_info['root_gb'] = 100
|
||||||
|
driver_internal_info = self.node.driver_internal_info
|
||||||
|
driver_internal_info['is_whole_disk_image'] = True
|
||||||
|
self.node.driver_internal_info = driver_internal_info
|
||||||
|
self.node.instance_info = i_info
|
||||||
|
self.node.save()
|
||||||
|
|
||||||
|
self.md5sum_mock = self.useFixture(fixtures.MockPatchObject(
|
||||||
|
fileutils, 'compute_file_checksum')).mock
|
||||||
|
self.md5sum_mock.return_value = 'fake md5'
|
||||||
|
self.cache_image_mock = self.useFixture(fixtures.MockPatchObject(
|
||||||
|
utils, 'cache_instance_image', autospec=True)).mock
|
||||||
|
self.cache_image_mock.return_value = (
|
||||||
|
'733d1c44-a2ea-414b-aca7-69decf20d810',
|
||||||
|
'/var/lib/ironic/images/{}/disk'.format(self.node.uuid))
|
||||||
|
self.ensure_tree_mock = self.useFixture(fixtures.MockPatchObject(
|
||||||
|
utils.fileutils, 'ensure_tree', autospec=True)).mock
|
||||||
|
self.create_link_mock = self.useFixture(fixtures.MockPatchObject(
|
||||||
|
common_utils, 'create_link_without_raise', autospec=True)).mock
|
||||||
|
|
||||||
|
cfg.CONF.set_override('http_url', 'http://172.172.24.10:8080',
|
||||||
|
group='deploy')
|
||||||
|
cfg.CONF.set_override('image_download_source', 'http', group='agent')
|
||||||
|
|
||||||
|
self.expected_url = '/'.join([cfg.CONF.deploy.http_url,
|
||||||
|
cfg.CONF.deploy.http_image_subdir,
|
||||||
|
self.node.uuid])
|
||||||
|
|
||||||
|
@mock.patch.object(image_service.HttpImageService, 'validate_href',
|
||||||
|
autospec=True)
|
||||||
|
@mock.patch.object(image_service, 'GlanceImageService', autospec=True)
|
||||||
|
def test_build_instance_info_no_force_raw(self, glance_mock,
|
||||||
|
validate_mock):
|
||||||
|
cfg.CONF.set_override('force_raw_images', False)
|
||||||
|
|
||||||
|
image_info = {'checksum': 'aa', 'disk_format': 'qcow2',
|
||||||
|
'container_format': 'bare', 'properties': {}}
|
||||||
|
glance_mock.return_value.show = mock.MagicMock(spec_set=[],
|
||||||
|
return_value=image_info)
|
||||||
|
|
||||||
|
with task_manager.acquire(
|
||||||
|
self.context, self.node.uuid, shared=False) as task:
|
||||||
|
|
||||||
|
instance_info = utils.build_instance_info_for_deploy(task)
|
||||||
|
|
||||||
|
glance_mock.assert_called_once_with(version=2,
|
||||||
|
context=task.context)
|
||||||
|
glance_mock.return_value.show.assert_called_once_with(
|
||||||
|
self.node.instance_info['image_source'])
|
||||||
|
self.cache_image_mock.assert_called_once_with(task.context,
|
||||||
|
task.node,
|
||||||
|
force_raw=False)
|
||||||
|
symlink_dir = utils._get_http_image_symlink_dir_path()
|
||||||
|
symlink_file = utils._get_http_image_symlink_file_path(
|
||||||
|
self.node.uuid)
|
||||||
|
image_path = utils._get_image_file_path(self.node.uuid)
|
||||||
|
self.ensure_tree_mock.assert_called_once_with(symlink_dir)
|
||||||
|
self.create_link_mock.assert_called_once_with(image_path,
|
||||||
|
symlink_file)
|
||||||
|
self.assertEqual(instance_info['image_checksum'], 'aa')
|
||||||
|
self.assertEqual(instance_info['image_disk_format'], 'qcow2')
|
||||||
|
self.md5sum_mock.assert_not_called()
|
||||||
|
validate_mock.assert_called_once_with(mock.ANY, self.expected_url,
|
||||||
|
secret=True)
|
||||||
|
|
||||||
|
@mock.patch.object(image_service.HttpImageService, 'validate_href',
|
||||||
|
autospec=True)
|
||||||
|
@mock.patch.object(image_service, 'GlanceImageService', autospec=True)
|
||||||
|
def test_build_instance_info_force_raw(self, glance_mock,
|
||||||
|
validate_mock):
|
||||||
|
cfg.CONF.set_override('force_raw_images', True)
|
||||||
|
|
||||||
|
image_info = {'checksum': 'aa', 'disk_format': 'qcow2',
|
||||||
|
'container_format': 'bare', 'properties': {}}
|
||||||
|
glance_mock.return_value.show = mock.MagicMock(spec_set=[],
|
||||||
|
return_value=image_info)
|
||||||
|
|
||||||
|
with task_manager.acquire(
|
||||||
|
self.context, self.node.uuid, shared=False) as task:
|
||||||
|
|
||||||
|
instance_info = utils.build_instance_info_for_deploy(task)
|
||||||
|
|
||||||
|
glance_mock.assert_called_once_with(version=2,
|
||||||
|
context=task.context)
|
||||||
|
glance_mock.return_value.show.assert_called_once_with(
|
||||||
|
self.node.instance_info['image_source'])
|
||||||
|
self.cache_image_mock.assert_called_once_with(task.context,
|
||||||
|
task.node,
|
||||||
|
force_raw=True)
|
||||||
|
symlink_dir = utils._get_http_image_symlink_dir_path()
|
||||||
|
symlink_file = utils._get_http_image_symlink_file_path(
|
||||||
|
self.node.uuid)
|
||||||
|
image_path = utils._get_image_file_path(self.node.uuid)
|
||||||
|
self.ensure_tree_mock.assert_called_once_with(symlink_dir)
|
||||||
|
self.create_link_mock.assert_called_once_with(image_path,
|
||||||
|
symlink_file)
|
||||||
|
self.assertEqual(instance_info['image_checksum'], 'fake md5')
|
||||||
|
self.assertEqual(instance_info['image_disk_format'], 'raw')
|
||||||
|
self.md5sum_mock.assert_called_once_with(image_path,
|
||||||
|
algorithm='md5')
|
||||||
|
validate_mock.assert_called_once_with(mock.ANY, self.expected_url,
|
||||||
|
secret=True)
|
||||||
|
|
||||||
|
|
||||||
class TestStorageInterfaceUtils(db_base.DbTestCase):
|
class TestStorageInterfaceUtils(db_base.DbTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestStorageInterfaceUtils, self).setUp()
|
super(TestStorageInterfaceUtils, self).setUp()
|
||||||
|
@ -47,7 +47,8 @@ class TestImageCacheFetch(base.TestCase):
|
|||||||
self.dest_dir = tempfile.mkdtemp()
|
self.dest_dir = tempfile.mkdtemp()
|
||||||
self.dest_path = os.path.join(self.dest_dir, 'dest')
|
self.dest_path = os.path.join(self.dest_dir, 'dest')
|
||||||
self.uuid = uuidutils.generate_uuid()
|
self.uuid = uuidutils.generate_uuid()
|
||||||
self.master_path = os.path.join(self.master_dir, self.uuid)
|
self.master_path = ''.join([os.path.join(self.master_dir, self.uuid),
|
||||||
|
'.converted'])
|
||||||
|
|
||||||
@mock.patch.object(image_cache, '_fetch', autospec=True)
|
@mock.patch.object(image_cache, '_fetch', autospec=True)
|
||||||
@mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
|
@mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
|
||||||
@ -81,6 +82,26 @@ class TestImageCacheFetch(base.TestCase):
|
|||||||
self.assertFalse(mock_download.called)
|
self.assertFalse(mock_download.called)
|
||||||
self.assertFalse(mock_clean_up.called)
|
self.assertFalse(mock_clean_up.called)
|
||||||
|
|
||||||
|
@mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
|
||||||
|
@mock.patch.object(image_cache.ImageCache, '_download_image',
|
||||||
|
autospec=True)
|
||||||
|
@mock.patch.object(os, 'link', autospec=True)
|
||||||
|
@mock.patch.object(image_cache, '_delete_dest_path_if_stale',
|
||||||
|
return_value=True, autospec=True)
|
||||||
|
@mock.patch.object(image_cache, '_delete_master_path_if_stale',
|
||||||
|
return_value=True, autospec=True)
|
||||||
|
def test_fetch_image_dest_and_master_uptodate_no_force_raw(
|
||||||
|
self, mock_cache_upd, mock_dest_upd, mock_link, mock_download,
|
||||||
|
mock_clean_up):
|
||||||
|
master_path = os.path.join(self.master_dir, self.uuid)
|
||||||
|
self.cache.fetch_image(self.uuid, self.dest_path, force_raw=False)
|
||||||
|
mock_cache_upd.assert_called_once_with(master_path, self.uuid,
|
||||||
|
None)
|
||||||
|
mock_dest_upd.assert_called_once_with(master_path, self.dest_path)
|
||||||
|
self.assertFalse(mock_link.called)
|
||||||
|
self.assertFalse(mock_download.called)
|
||||||
|
self.assertFalse(mock_clean_up.called)
|
||||||
|
|
||||||
@mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
|
@mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
|
||||||
@mock.patch.object(image_cache.ImageCache, '_download_image',
|
@mock.patch.object(image_cache.ImageCache, '_download_image',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@ -149,13 +170,29 @@ class TestImageCacheFetch(base.TestCase):
|
|||||||
href = u'http://abc.com/ubuntu.qcow2'
|
href = u'http://abc.com/ubuntu.qcow2'
|
||||||
href_encoded = href.encode('utf-8') if six.PY2 else href
|
href_encoded = href.encode('utf-8') if six.PY2 else href
|
||||||
href_converted = str(uuid.uuid5(uuid.NAMESPACE_URL, href_encoded))
|
href_converted = str(uuid.uuid5(uuid.NAMESPACE_URL, href_encoded))
|
||||||
master_path = os.path.join(self.master_dir, href_converted)
|
master_path = ''.join([os.path.join(self.master_dir, href_converted),
|
||||||
|
'.converted'])
|
||||||
self.cache.fetch_image(href, self.dest_path)
|
self.cache.fetch_image(href, self.dest_path)
|
||||||
mock_download.assert_called_once_with(
|
mock_download.assert_called_once_with(
|
||||||
self.cache, href, master_path, self.dest_path,
|
self.cache, href, master_path, self.dest_path,
|
||||||
ctx=None, force_raw=True)
|
ctx=None, force_raw=True)
|
||||||
self.assertTrue(mock_clean_up.called)
|
self.assertTrue(mock_clean_up.called)
|
||||||
|
|
||||||
|
@mock.patch.object(image_cache.ImageCache, 'clean_up', autospec=True)
|
||||||
|
@mock.patch.object(image_cache.ImageCache, '_download_image',
|
||||||
|
autospec=True)
|
||||||
|
def test_fetch_image_not_uuid_no_force_raw(self, mock_download,
|
||||||
|
mock_clean_up):
|
||||||
|
href = u'http://abc.com/ubuntu.qcow2'
|
||||||
|
href_encoded = href.encode('utf-8') if six.PY2 else href
|
||||||
|
href_converted = str(uuid.uuid5(uuid.NAMESPACE_URL, href_encoded))
|
||||||
|
master_path = os.path.join(self.master_dir, href_converted)
|
||||||
|
self.cache.fetch_image(href, self.dest_path, force_raw=False)
|
||||||
|
mock_download.assert_called_once_with(
|
||||||
|
self.cache, href, master_path, self.dest_path,
|
||||||
|
ctx=None, force_raw=False)
|
||||||
|
self.assertTrue(mock_clean_up.called)
|
||||||
|
|
||||||
@mock.patch.object(image_cache, '_fetch', autospec=True)
|
@mock.patch.object(image_cache, '_fetch', autospec=True)
|
||||||
def test__download_image(self, mock_fetch):
|
def test__download_image(self, mock_fetch):
|
||||||
def _fake_fetch(ctx, uuid, tmp_path, *args):
|
def _fake_fetch(ctx, uuid, tmp_path, *args):
|
||||||
|
@ -83,13 +83,13 @@ class IscsiDeployPrivateMethodsTestCase(db_base.DbTestCase):
|
|||||||
def test__get_image_dir_path(self):
|
def test__get_image_dir_path(self):
|
||||||
self.assertEqual(os.path.join(CONF.pxe.images_path,
|
self.assertEqual(os.path.join(CONF.pxe.images_path,
|
||||||
self.node.uuid),
|
self.node.uuid),
|
||||||
iscsi_deploy._get_image_dir_path(self.node.uuid))
|
deploy_utils._get_image_dir_path(self.node.uuid))
|
||||||
|
|
||||||
def test__get_image_file_path(self):
|
def test__get_image_file_path(self):
|
||||||
self.assertEqual(os.path.join(CONF.pxe.images_path,
|
self.assertEqual(os.path.join(CONF.pxe.images_path,
|
||||||
self.node.uuid,
|
self.node.uuid,
|
||||||
'disk'),
|
'disk'),
|
||||||
iscsi_deploy._get_image_file_path(self.node.uuid))
|
deploy_utils._get_image_file_path(self.node.uuid))
|
||||||
|
|
||||||
|
|
||||||
class IscsiDeployMethodsTestCase(db_base.DbTestCase):
|
class IscsiDeployMethodsTestCase(db_base.DbTestCase):
|
||||||
@ -115,7 +115,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
|
|||||||
task.node.instance_info['root_gb'] = 1
|
task.node.instance_info['root_gb'] = 1
|
||||||
iscsi_deploy.check_image_size(task)
|
iscsi_deploy.check_image_size(task)
|
||||||
get_image_mb_mock.assert_called_once_with(
|
get_image_mb_mock.assert_called_once_with(
|
||||||
iscsi_deploy._get_image_file_path(task.node.uuid))
|
deploy_utils._get_image_file_path(task.node.uuid))
|
||||||
|
|
||||||
@mock.patch.object(disk_utils, 'get_image_mb', autospec=True)
|
@mock.patch.object(disk_utils, 'get_image_mb', autospec=True)
|
||||||
def test_check_image_size_whole_disk_image(self, get_image_mb_mock):
|
def test_check_image_size_whole_disk_image(self, get_image_mb_mock):
|
||||||
@ -138,7 +138,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
|
|||||||
iscsi_deploy.check_image_size,
|
iscsi_deploy.check_image_size,
|
||||||
task)
|
task)
|
||||||
get_image_mb_mock.assert_called_once_with(
|
get_image_mb_mock.assert_called_once_with(
|
||||||
iscsi_deploy._get_image_file_path(task.node.uuid))
|
deploy_utils._get_image_file_path(task.node.uuid))
|
||||||
|
|
||||||
@mock.patch.object(deploy_utils, 'fetch_images', autospec=True)
|
@mock.patch.object(deploy_utils, 'fetch_images', autospec=True)
|
||||||
def test_cache_instance_images_master_path(self, mock_fetch_image):
|
def test_cache_instance_images_master_path(self, mock_fetch_image):
|
||||||
@ -149,7 +149,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
|
|||||||
group='pxe')
|
group='pxe')
|
||||||
fileutils.ensure_tree(CONF.pxe.instance_master_path)
|
fileutils.ensure_tree(CONF.pxe.instance_master_path)
|
||||||
|
|
||||||
(uuid, image_path) = iscsi_deploy.cache_instance_image(None, self.node)
|
(uuid, image_path) = deploy_utils.cache_instance_image(None, self.node)
|
||||||
mock_fetch_image.assert_called_once_with(None,
|
mock_fetch_image.assert_called_once_with(None,
|
||||||
mock.ANY,
|
mock.ANY,
|
||||||
[(uuid, image_path)], True)
|
[(uuid, image_path)], True)
|
||||||
@ -161,11 +161,11 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
@mock.patch.object(ironic_utils, 'unlink_without_raise', autospec=True)
|
@mock.patch.object(ironic_utils, 'unlink_without_raise', autospec=True)
|
||||||
@mock.patch.object(utils, 'rmtree_without_raise', autospec=True)
|
@mock.patch.object(utils, 'rmtree_without_raise', autospec=True)
|
||||||
@mock.patch.object(iscsi_deploy, 'InstanceImageCache', autospec=True)
|
@mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True)
|
||||||
def test_destroy_images(self, mock_cache, mock_rmtree, mock_unlink):
|
def test_destroy_images(self, mock_cache, mock_rmtree, mock_unlink):
|
||||||
self.config(images_path='/path', group='pxe')
|
self.config(images_path='/path', group='pxe')
|
||||||
|
|
||||||
iscsi_deploy.destroy_images('uuid')
|
deploy_utils.destroy_images('uuid')
|
||||||
|
|
||||||
mock_cache.return_value.clean_up.assert_called_once_with()
|
mock_cache.return_value.clean_up.assert_called_once_with()
|
||||||
mock_unlink.assert_called_once_with('/path/uuid/disk')
|
mock_unlink.assert_called_once_with('/path/uuid/disk')
|
||||||
@ -173,7 +173,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||||
@mock.patch.object(iscsi_deploy, '_save_disk_layout', autospec=True)
|
@mock.patch.object(iscsi_deploy, '_save_disk_layout', autospec=True)
|
||||||
@mock.patch.object(iscsi_deploy, 'InstanceImageCache', autospec=True)
|
@mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True)
|
||||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||||
@mock.patch.object(deploy_utils, 'deploy_partition_image', autospec=True)
|
@mock.patch.object(deploy_utils, 'deploy_partition_image', autospec=True)
|
||||||
def test_continue_deploy_fail(
|
def test_continue_deploy_fail(
|
||||||
@ -206,7 +206,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||||
@mock.patch.object(iscsi_deploy, '_save_disk_layout', autospec=True)
|
@mock.patch.object(iscsi_deploy, '_save_disk_layout', autospec=True)
|
||||||
@mock.patch.object(iscsi_deploy, 'InstanceImageCache', autospec=True)
|
@mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True)
|
||||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||||
@mock.patch.object(deploy_utils, 'deploy_partition_image', autospec=True)
|
@mock.patch.object(deploy_utils, 'deploy_partition_image', autospec=True)
|
||||||
def test_continue_deploy_unexpected_fail(
|
def test_continue_deploy_unexpected_fail(
|
||||||
@ -237,7 +237,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||||
@mock.patch.object(iscsi_deploy, '_save_disk_layout', autospec=True)
|
@mock.patch.object(iscsi_deploy, '_save_disk_layout', autospec=True)
|
||||||
@mock.patch.object(iscsi_deploy, 'InstanceImageCache', autospec=True)
|
@mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True)
|
||||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||||
@mock.patch.object(deploy_utils, 'deploy_partition_image', autospec=True)
|
@mock.patch.object(deploy_utils, 'deploy_partition_image', autospec=True)
|
||||||
def test_continue_deploy_fail_no_root_uuid_or_disk_id(
|
def test_continue_deploy_fail_no_root_uuid_or_disk_id(
|
||||||
@ -267,7 +267,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
@mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True)
|
||||||
@mock.patch.object(iscsi_deploy, '_save_disk_layout', autospec=True)
|
@mock.patch.object(iscsi_deploy, '_save_disk_layout', autospec=True)
|
||||||
@mock.patch.object(iscsi_deploy, 'InstanceImageCache', autospec=True)
|
@mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True)
|
||||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||||
@mock.patch.object(deploy_utils, 'deploy_partition_image', autospec=True)
|
@mock.patch.object(deploy_utils, 'deploy_partition_image', autospec=True)
|
||||||
def test_continue_deploy_fail_empty_root_uuid(
|
def test_continue_deploy_fail_empty_root_uuid(
|
||||||
@ -298,7 +298,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
|
|||||||
@mock.patch.object(iscsi_deploy, '_save_disk_layout', autospec=True)
|
@mock.patch.object(iscsi_deploy, '_save_disk_layout', autospec=True)
|
||||||
@mock.patch.object(iscsi_deploy, 'LOG', autospec=True)
|
@mock.patch.object(iscsi_deploy, 'LOG', autospec=True)
|
||||||
@mock.patch.object(iscsi_deploy, 'get_deploy_info', autospec=True)
|
@mock.patch.object(iscsi_deploy, 'get_deploy_info', autospec=True)
|
||||||
@mock.patch.object(iscsi_deploy, 'InstanceImageCache', autospec=True)
|
@mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True)
|
||||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||||
@mock.patch.object(deploy_utils, 'deploy_partition_image', autospec=True)
|
@mock.patch.object(deploy_utils, 'deploy_partition_image', autospec=True)
|
||||||
def test_continue_deploy(self, deploy_mock, power_mock, mock_image_cache,
|
def test_continue_deploy(self, deploy_mock, power_mock, mock_image_cache,
|
||||||
@ -350,7 +350,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
@mock.patch.object(iscsi_deploy, 'LOG', autospec=True)
|
@mock.patch.object(iscsi_deploy, 'LOG', autospec=True)
|
||||||
@mock.patch.object(iscsi_deploy, 'get_deploy_info', autospec=True)
|
@mock.patch.object(iscsi_deploy, 'get_deploy_info', autospec=True)
|
||||||
@mock.patch.object(iscsi_deploy, 'InstanceImageCache', autospec=True)
|
@mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True)
|
||||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||||
@mock.patch.object(deploy_utils, 'deploy_disk_image', autospec=True)
|
@mock.patch.object(deploy_utils, 'deploy_disk_image', autospec=True)
|
||||||
def test_continue_deploy_whole_disk_image(
|
def test_continue_deploy_whole_disk_image(
|
||||||
@ -703,7 +703,7 @@ class ISCSIDeployTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||||
@mock.patch.object(iscsi_deploy, 'check_image_size', autospec=True)
|
@mock.patch.object(iscsi_deploy, 'check_image_size', autospec=True)
|
||||||
@mock.patch.object(iscsi_deploy, 'cache_instance_image', autospec=True)
|
@mock.patch.object(deploy_utils, 'cache_instance_image', autospec=True)
|
||||||
def test_deploy(self, mock_cache_instance_image,
|
def test_deploy(self, mock_cache_instance_image,
|
||||||
mock_check_image_size, mock_node_power_action):
|
mock_check_image_size, mock_node_power_action):
|
||||||
with task_manager.acquire(self.context,
|
with task_manager.acquire(self.context,
|
||||||
@ -728,7 +728,7 @@ class ISCSIDeployTestCase(db_base.DbTestCase):
|
|||||||
spec_set=True, autospec=True)
|
spec_set=True, autospec=True)
|
||||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||||
@mock.patch.object(iscsi_deploy, 'check_image_size', autospec=True)
|
@mock.patch.object(iscsi_deploy, 'check_image_size', autospec=True)
|
||||||
@mock.patch.object(iscsi_deploy, 'cache_instance_image', autospec=True)
|
@mock.patch.object(deploy_utils, 'cache_instance_image', autospec=True)
|
||||||
def test_deploy_storage_check_write_image_false(self,
|
def test_deploy_storage_check_write_image_false(self,
|
||||||
mock_cache_instance_image,
|
mock_cache_instance_image,
|
||||||
mock_check_image_size,
|
mock_check_image_size,
|
||||||
@ -790,7 +790,7 @@ class ISCSIDeployTestCase(db_base.DbTestCase):
|
|||||||
@mock.patch('ironic.common.dhcp_factory.DHCPFactory.clean_dhcp')
|
@mock.patch('ironic.common.dhcp_factory.DHCPFactory.clean_dhcp')
|
||||||
@mock.patch.object(pxe.PXEBoot, 'clean_up_instance', autospec=True)
|
@mock.patch.object(pxe.PXEBoot, 'clean_up_instance', autospec=True)
|
||||||
@mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk', autospec=True)
|
@mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk', autospec=True)
|
||||||
@mock.patch.object(iscsi_deploy, 'destroy_images', autospec=True)
|
@mock.patch.object(deploy_utils, 'destroy_images', autospec=True)
|
||||||
def test_clean_up(self, destroy_images_mock, clean_up_ramdisk_mock,
|
def test_clean_up(self, destroy_images_mock, clean_up_ramdisk_mock,
|
||||||
clean_up_instance_mock, clean_dhcp_mock,
|
clean_up_instance_mock, clean_dhcp_mock,
|
||||||
set_dhcp_provider_mock):
|
set_dhcp_provider_mock):
|
||||||
@ -982,9 +982,9 @@ class CleanUpFullFlowTestCase(db_base.DbTestCase):
|
|||||||
os.makedirs(self.node_tftp_dir)
|
os.makedirs(self.node_tftp_dir)
|
||||||
self.kernel_path = os.path.join(self.node_tftp_dir,
|
self.kernel_path = os.path.join(self.node_tftp_dir,
|
||||||
'kernel')
|
'kernel')
|
||||||
self.node_image_dir = iscsi_deploy._get_image_dir_path(self.node.uuid)
|
self.node_image_dir = deploy_utils._get_image_dir_path(self.node.uuid)
|
||||||
os.makedirs(self.node_image_dir)
|
os.makedirs(self.node_image_dir)
|
||||||
self.image_path = iscsi_deploy._get_image_file_path(self.node.uuid)
|
self.image_path = deploy_utils._get_image_file_path(self.node.uuid)
|
||||||
self.config_path = pxe_utils.get_pxe_config_file_path(self.node.uuid)
|
self.config_path = pxe_utils.get_pxe_config_file_path(self.node.uuid)
|
||||||
self.mac_path = pxe_utils._get_pxe_mac_path(self.port.address)
|
self.mac_path = pxe_utils._get_pxe_mac_path(self.port.address)
|
||||||
|
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Adds the ability to provision with ``direct`` deploy interface and custom
|
||||||
|
HTTP service running at ironic conductor node. A new configuration option
|
||||||
|
``[agent]image_download_source`` is introduced. When set to ``swift``,
|
||||||
|
the ``direct`` deploy interface uses tempurl generated via the Object
|
||||||
|
service as the source of instance image during provisioning, this is the
|
||||||
|
default configuration. When set to ``http``, the ``direct`` deploy
|
||||||
|
interface downloads instance image from the Image service, and caches the
|
||||||
|
image in the ironic conductor node. The cached instance images are
|
||||||
|
referenced by symbolic links located at subdirectory
|
||||||
|
``[deploy]http_image_subdir`` under path ``[deploy]http_root``. The custom
|
||||||
|
HTTP server running at ironic conductor node is supposed to be configured
|
||||||
|
properly to make IPA has unauthenticated access to image URL described
|
||||||
|
above.
|
@ -247,3 +247,22 @@
|
|||||||
s-container: True
|
s-container: True
|
||||||
s-object: True
|
s-object: True
|
||||||
s-proxy: True
|
s-proxy: True
|
||||||
|
|
||||||
|
- job:
|
||||||
|
name: ironic-tempest-dsvm-ipa-wholedisk-bios-agent_ipmitool-tinyipa-indirect
|
||||||
|
description: ironic-tempest-dsvm-ipa-wholedisk-bios-agent_ipmitool-tinyipa-indirect
|
||||||
|
parent: ironic-tempest-dsvm-ipa-wholedisk-bios-agent_ipmitool-tinyipa
|
||||||
|
timeout: 5400
|
||||||
|
vars:
|
||||||
|
devstack_localrc:
|
||||||
|
IRONIC_AGENT_IMAGE_DOWNLOAD_SOURCE: http
|
||||||
|
|
||||||
|
- job:
|
||||||
|
name: ironic-tempest-dsvm-ipa-partition-bios-agent_ipmitool-tinyipa-indirect
|
||||||
|
description: ironic-tempest-dsvm-ipa-partition-bios-agent_ipmitool-tinyipa-indirect
|
||||||
|
parent: ironic-tempest-dsvm-ipa-wholedisk-bios-agent_ipmitool-tinyipa
|
||||||
|
timeout: 5400
|
||||||
|
vars:
|
||||||
|
devstack_localrc:
|
||||||
|
IRONIC_AGENT_IMAGE_DOWNLOAD_SOURCE: http
|
||||||
|
IRONIC_TEMPEST_WHOLE_DISK_IMAGE: False
|
||||||
|
@ -21,6 +21,8 @@
|
|||||||
- ironic-tempest-dsvm-ipa-partition-uefi-pxe_ipmitool-tinyipa
|
- ironic-tempest-dsvm-ipa-partition-uefi-pxe_ipmitool-tinyipa
|
||||||
- ironic-tempest-dsvm-ipa-wholedisk-agent_ipmitool-tinyipa-multinode
|
- ironic-tempest-dsvm-ipa-wholedisk-agent_ipmitool-tinyipa-multinode
|
||||||
- ironic-tempest-dsvm-ipa-wholedisk-bios-agent_ipmitool-tinyipa
|
- ironic-tempest-dsvm-ipa-wholedisk-bios-agent_ipmitool-tinyipa
|
||||||
|
- ironic-tempest-dsvm-ipa-wholedisk-bios-agent_ipmitool-tinyipa-indirect
|
||||||
|
- ironic-tempest-dsvm-ipa-partition-bios-agent_ipmitool-tinyipa-indirect
|
||||||
# Non-voting jobs
|
# Non-voting jobs
|
||||||
- ironic-tempest-dsvm-ipa-wholedisk-bios-pxe_snmp-tinyipa:
|
- ironic-tempest-dsvm-ipa-wholedisk-bios-pxe_snmp-tinyipa:
|
||||||
voting: false
|
voting: false
|
||||||
@ -44,6 +46,8 @@
|
|||||||
- ironic-tempest-dsvm-ipa-partition-uefi-pxe_ipmitool-tinyipa
|
- ironic-tempest-dsvm-ipa-partition-uefi-pxe_ipmitool-tinyipa
|
||||||
- ironic-tempest-dsvm-ipa-wholedisk-agent_ipmitool-tinyipa-multinode
|
- ironic-tempest-dsvm-ipa-wholedisk-agent_ipmitool-tinyipa-multinode
|
||||||
- ironic-tempest-dsvm-ipa-wholedisk-bios-agent_ipmitool-tinyipa
|
- ironic-tempest-dsvm-ipa-wholedisk-bios-agent_ipmitool-tinyipa
|
||||||
|
- ironic-tempest-dsvm-ipa-wholedisk-bios-agent_ipmitool-tinyipa-indirect
|
||||||
|
- ironic-tempest-dsvm-ipa-partition-bios-agent_ipmitool-tinyipa-indirect
|
||||||
- openstack-tox-lower-constraints
|
- openstack-tox-lower-constraints
|
||||||
- openstack-tox-cover
|
- openstack-tox-cover
|
||||||
experimental:
|
experimental:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user