summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSirushti Murugesan <sirushti.murugesan@hp.com>2015-01-26 23:44:14 +0530
committerSirushti Murugesan <sirushti.murugesan@hp.com>2015-03-12 22:41:28 +0530
commit222c84fff52be8383b26495c37df28cc5a0f98b9 (patch)
treef56be2b60574c38d60faf44720b4e16b69182296
parent01af4f8808916a3e8069398da20ece515da28fbf (diff)
downloadironic-222c84fff52be8383b26495c37df28cc5a0f98b9.tar.gz
Adds support for deploying whole disk images
Ironic decides whether it should deploy whole disk images or partition based images based on the presence of a kernel and a ramdisk. Partition based images are still deployed the way it is currently. No change made in the deployment workflow of partition based images. Whole Disk Images are dumped directly on the lun of the disk presented so that they can be localbooted. Separate PXE-Config entries have been added for whole disk images which when used to deploy whole disk images chainloads onto the hard drive that was exposed by the deploy ramdisk. Partially-Implements blueprint whole-disk-image-support Change-Id: I4478eff302a0ffeb3e9f2077a41170671863c478
-rw-r--r--doc/source/deploy/install-guide.rst20
-rw-r--r--etc/ironic/rootwrap.d/ironic-utils.filters1
-rw-r--r--ironic/common/image_service.py7
-rw-r--r--ironic/common/images.py31
-rw-r--r--ironic/common/pxe_utils.py4
-rw-r--r--ironic/conductor/manager.py39
-rw-r--r--ironic/drivers/modules/agent_base_vendor.py15
-rw-r--r--ironic/drivers/modules/deploy_utils.py148
-rw-r--r--ironic/drivers/modules/elilo_efi_pxe_config.template6
-rw-r--r--ironic/drivers/modules/ipxe_config.template7
-rw-r--r--ironic/drivers/modules/iscsi_deploy.py70
-rw-r--r--ironic/drivers/modules/pxe.py84
-rw-r--r--ironic/drivers/modules/pxe_config.template7
-rw-r--r--ironic/tests/conductor/test_manager.py430
-rw-r--r--ironic/tests/db/utils.py6
-rw-r--r--ironic/tests/drivers/pxe_config.template7
-rw-r--r--ironic/tests/drivers/test_deploy_utils.py362
-rw-r--r--ironic/tests/drivers/test_iscsi_deploy.py193
-rw-r--r--ironic/tests/drivers/test_pxe.py228
-rw-r--r--ironic/tests/test_images.py63
20 files changed, 1283 insertions, 445 deletions
diff --git a/doc/source/deploy/install-guide.rst b/doc/source/deploy/install-guide.rst
index 0ba4ee8ad..cc179d5e2 100644
--- a/doc/source/deploy/install-guide.rst
+++ b/doc/source/deploy/install-guide.rst
@@ -513,6 +513,13 @@ them to Glance service:
kernel_id=$MY_VMLINUZ_UUID --property \
ramdisk_id=$MY_INITRD_UUID < my-image.qcow2
+ - *Note:* To deploy a whole disk image, a kernel_id and a ramdisk_id
+ shouldn't be associated with the image. An example is as follows::
+
+ glance image-create --name my-whole-disk-image --is-public True \
+ --disk-format qcow2 \
+ --container-format bare < my-whole-disk-image.qcow2
+
3. Add the deploy images to glance
Add the *my-deploy-ramdisk.kernel* and
@@ -612,6 +619,19 @@ node(s) where ``ironic-conductor`` is running.
Ubuntu (14.10 and after):
sudo cp /usr/lib/PXELINUX/pxelinux.0 /tftpboot
+#. If whole disk images need to be deployed via PXE-netboot, copy the
+ chain.c32 image to ``/tftpboot`` to support it. The chain.c32 image
+ might be found at::
+
+ Ubuntu (Up to and including 14.04):
+ sudo cp /usr/lib/syslinux/chain.c32 /tftpboot
+
+ Ubuntu (14.10 and after):
+ sudo cp /usr/lib/syslinux/modules/bios/chain.c32 /tftpboot
+
+ Fedora:
+ sudo cp /boot/extlinux/chain.c32 /tftpboot
+
#. If the version of syslinux is **greater than** 4 we also need to make sure
that we copy the library modules into the ``/tftpboot`` directory [2]_
[1]_::
diff --git a/etc/ironic/rootwrap.d/ironic-utils.filters b/etc/ironic/rootwrap.d/ironic-utils.filters
index 01a125842..972ddf5cf 100644
--- a/etc/ironic/rootwrap.d/ironic-utils.filters
+++ b/etc/ironic/rootwrap.d/ironic-utils.filters
@@ -6,6 +6,7 @@
iscsiadm: CommandFilter, iscsiadm, root
blkid: CommandFilter, blkid, root
blockdev: CommandFilter, blockdev, root
+hexdump: CommandFilter, hexdump, root
# ironic/common/utils.py
mkswap: CommandFilter, mkswap, root
diff --git a/ironic/common/image_service.py b/ironic/common/image_service.py
index 9f3f79752..57ab194d7 100644
--- a/ironic/common/image_service.py
+++ b/ironic/common/image_service.py
@@ -35,6 +35,11 @@ LOG = logging.getLogger(__name__)
IMAGE_CHUNK_SIZE = 1024 * 1024 # 1mb
+CONF = cfg.CONF
+# Import this opt early so that it is available when registering
+# glance_opts below.
+CONF.import_opt('my_ip', 'ironic.netconf')
+
glance_opts = [
cfg.StrOpt('glance_host',
default='$my_ip',
@@ -64,8 +69,6 @@ glance_opts = [
'Set to https for SSL.'),
]
-
-CONF = cfg.CONF
CONF.register_opts(glance_opts, group='glance')
diff --git a/ironic/common/images.py b/ironic/common/images.py
index 8919055e0..149091009 100644
--- a/ironic/common/images.py
+++ b/ironic/common/images.py
@@ -27,6 +27,7 @@ from oslo_concurrency import processutils
from oslo_config import cfg
from ironic.common import exception
+from ironic.common.glance_service import service_utils as glance_utils
from ironic.common.i18n import _
from ironic.common.i18n import _LE
from ironic.common import image_service as service
@@ -377,3 +378,33 @@ def create_boot_iso(context, output_filename, kernel_href,
create_isolinux_image(output_filename, kernel_path,
ramdisk_path, params)
+
+
+def is_whole_disk_image(ctx, instance_info):
+ """Find out if the image is a partition image or a whole disk image.
+
+ :param ctx: an admin context
+ :param instance_info: a node's instance info dict
+
+ :returns True for whole disk images and False for partition images
+ and None on no image_source or Error.
+ """
+ image_source = instance_info.get('image_source')
+ if not image_source:
+ return
+
+ is_whole_disk_image = False
+ if glance_utils.is_glance_image(image_source):
+ try:
+ iproperties = get_image_properties(ctx, image_source)
+ except Exception:
+ return
+ is_whole_disk_image = (not iproperties.get('kernel_id') and
+ not iproperties.get('ramdisk_id'))
+ else:
+ # Non glance image ref
+ if (not instance_info.get('kernel') and
+ not instance_info.get('ramdisk')):
+ is_whole_disk_image = True
+
+ return is_whole_disk_image
diff --git a/ironic/common/pxe_utils.py b/ironic/common/pxe_utils.py
index ff7970756..3a214c3da 100644
--- a/ironic/common/pxe_utils.py
+++ b/ironic/common/pxe_utils.py
@@ -68,7 +68,9 @@ def _build_pxe_config(pxe_options, template):
env = jinja2.Environment(loader=jinja2.FileSystemLoader(tmpl_path))
template = env.get_template(tmpl_file)
return template.render({'pxe_options': pxe_options,
- 'ROOT': '{{ ROOT }}'})
+ 'ROOT': '{{ ROOT }}',
+ 'DISK_IDENTIFIER': '{{ DISK_IDENTIFIER }}',
+ })
def _link_mac_pxe_configs(task):
diff --git a/ironic/conductor/manager.py b/ironic/conductor/manager.py
index 837f46030..baf824bc4 100644
--- a/ironic/conductor/manager.py
+++ b/ironic/conductor/manager.py
@@ -66,6 +66,7 @@ from ironic.common.i18n import _LC
from ironic.common.i18n import _LE
from ironic.common.i18n import _LI
from ironic.common.i18n import _LW
+from ironic.common import images
from ironic.common import keystone
from ironic.common import rpc
from ironic.common import states
@@ -667,14 +668,6 @@ class ConductorManager(periodic_task.PeriodicTasks):
if node.maintenance:
raise exception.NodeInMaintenance(op=_('provisioning'),
node=node.uuid)
- try:
- task.driver.power.validate(task)
- task.driver.deploy.validate(task)
- except (exception.InvalidParameterValue,
- exception.MissingParameterValue) as e:
- raise exception.InstanceDeployFailure(_(
- "RPC do_node_deploy failed to validate deploy or "
- "power info. Error: %(msg)s") % {'msg': e})
if rebuild:
event = 'rebuild'
@@ -689,10 +682,29 @@ class ConductorManager(periodic_task.PeriodicTasks):
instance_info.pop('kernel', None)
instance_info.pop('ramdisk', None)
node.instance_info = instance_info
- node.save()
else:
event = 'deploy'
+ driver_internal_info = node.driver_internal_info
+ # Infer the image type to make sure the deploy driver
+ # validates only the necessary variables for different
+ # image types.
+ # NOTE(sirushtim): The iwdi variable can be None. It's upto
+ # the deploy driver to validate this.
+ iwdi = images.is_whole_disk_image(context, node.instance_info)
+ driver_internal_info['is_whole_disk_image'] = iwdi
+ node.driver_internal_info = driver_internal_info
+ node.save()
+
+ try:
+ task.driver.power.validate(task)
+ task.driver.deploy.validate(task)
+ except (exception.InvalidParameterValue,
+ exception.MissingParameterValue) as e:
+ raise exception.InstanceDeployFailure(_(
+ "RPC do_node_deploy failed to validate deploy or "
+ "power info. Error: %(msg)s") % {'msg': e})
+
LOG.debug("do_node_deploy Calling event: %(event)s for node: "
"%(node)s", {'event': event, 'node': node.uuid})
try:
@@ -1040,6 +1052,15 @@ class ConductorManager(periodic_task.PeriodicTasks):
node_id)
ret_dict = {}
with task_manager.acquire(context, node_id, shared=True) as task:
+ # NOTE(sirushtim): the is_whole_disk_image variable is needed by
+ # deploy drivers for doing their validate(). Since the deploy
+ # isn't being done yet and the driver information could change in
+ # the meantime, we don't know if the is_whole_disk_image value will
+ # change or not. It isn't saved to the DB, but only used with this
+ # node instance for the current validations.
+ iwdi = images.is_whole_disk_image(context,
+ task.node.instance_info)
+ task.node.driver_internal_info['is_whole_disk_image'] = iwdi
for iface_name in (task.driver.core_interfaces +
task.driver.standard_interfaces):
iface = getattr(task.driver, iface_name, None)
diff --git a/ironic/drivers/modules/agent_base_vendor.py b/ironic/drivers/modules/agent_base_vendor.py
index fc33ca9fc..3b624d07e 100644
--- a/ironic/drivers/modules/agent_base_vendor.py
+++ b/ironic/drivers/modules/agent_base_vendor.py
@@ -360,13 +360,14 @@ class BaseAgentVendor(base.VendorInterface):
on encountering error while setting the boot device on the node.
"""
node = task.node
- result = self._client.install_bootloader(node, root_uuid)
- if result['command_status'] == 'FAILED':
- msg = (_("Failed to install a bootloader when "
- "deploying node %(node)s. Error: %(error)s") %
- {'node': node.uuid,
- 'error': result['command_error']})
- self._log_and_raise_deployment_error(task, msg)
+ if not node.driver_internal_info.get('is_whole_disk_image'):
+ result = self._client.install_bootloader(node, root_uuid)
+ if result['command_status'] == 'FAILED':
+ msg = (_("Failed to install a bootloader when "
+ "deploying node %(node)s. Error: %(error)s") %
+ {'node': node.uuid,
+ 'error': result['command_error']})
+ self._log_and_raise_deployment_error(task, msg)
try:
deploy_utils.try_set_boot_device(task, boot_devices.DISK)
diff --git a/ironic/drivers/modules/deploy_utils.py b/ironic/drivers/modules/deploy_utils.py
index e3efbbd8a..8318ae754 100644
--- a/ironic/drivers/modules/deploy_utils.py
+++ b/ironic/drivers/modules/deploy_utils.py
@@ -15,6 +15,7 @@
import base64
+import contextlib
import gzip
import math
import os
@@ -185,6 +186,28 @@ def delete_iscsi(portal_address, portal_port, target_iqn):
delay_on_retry=True)
+def get_disk_identifier(dev):
+ """Get the disk identifier from the disk being exposed by the ramdisk.
+
+ This disk identifier is appended to the pxe config which will then be
+ used by chain.c32 to detect the correct disk to chainload. This is helpful
+ in deployments to nodes with multiple disks.
+
+ http://www.syslinux.org/wiki/index.php/Comboot/chain.c32#mbr:
+
+ :param dev: Path for the already populated disk device.
+ :returns The Disk Identifier.
+ """
+ disk_identifier = utils.execute('hexdump', '-s', '440', '-n', '4',
+ '-e', '''\"0x%08x\"''',
+ dev,
+ run_as_root=True,
+ check_exit_code=[0],
+ attempts=5,
+ delay_on_retry=True)
+ return disk_identifier[0]
+
+
def make_partitions(dev, root_mb, swap_mb, ephemeral_mb,
configdrive_mb, commit=True, boot_option="netboot"):
"""Partition the disk device.
@@ -290,26 +313,61 @@ def block_uuid(dev):
return out.strip()
-def switch_pxe_config(path, root_uuid, boot_mode):
- """Switch a pxe config from deployment mode to service mode."""
+def _replace_lines_in_file(path, regex_pattern, replacement):
with open(path) as f:
lines = f.readlines()
+
+ compiled_pattern = re.compile(regex_pattern)
+ with open(path, 'w') as f:
+ for line in lines:
+ line = compiled_pattern.sub(replacement, line)
+ f.write(line)
+
+
+def _replace_root_uuid(path, root_uuid):
root = 'UUID=%s' % root_uuid
- rre = re.compile(r'\{\{ ROOT \}\}')
+ pattern = r'\{\{ ROOT \}\}'
+ _replace_lines_in_file(path, pattern, root)
+
+
+def _replace_boot_line(path, boot_mode, is_whole_disk_image):
+ if is_whole_disk_image:
+ boot_disk_type = 'boot_whole_disk'
+ else:
+ boot_disk_type = 'boot_partition'
if boot_mode == 'uefi':
- dre = re.compile('^default=.*$')
- boot_line = 'default=boot'
+ pattern = '^default=.*$'
+ boot_line = 'default=%s' % boot_disk_type
else:
pxe_cmd = 'goto' if CONF.pxe.ipxe_enabled else 'default'
- dre = re.compile('^%s .*$' % pxe_cmd)
- boot_line = '%s boot' % pxe_cmd
+ pattern = '^%s .*$' % pxe_cmd
+ boot_line = '%s %s' % (pxe_cmd, boot_disk_type)
- with open(path, 'w') as f:
- for line in lines:
- line = rre.sub(root, line)
- line = dre.sub(boot_line, line)
- f.write(line)
+ _replace_lines_in_file(path, pattern, boot_line)
+
+
+def _replace_disk_identifier(path, disk_identifier):
+ pattern = r'\{\{ DISK_IDENTIFIER \}\}'
+ _replace_lines_in_file(path, pattern, disk_identifier)
+
+
+def switch_pxe_config(path, root_uuid_or_disk_id, boot_mode,
+ is_whole_disk_image):
+ """Switch a pxe config from deployment mode to service mode.
+
+ :param path: path to the pxe config file in tftpboot.
+ :param root_uuid_or_disk_id: root uuid in case of partition image or
+ disk_id in case of whole disk image.
+ :param boot_mode: if boot mode is uefi or bios.
+ :param is_whole_disk_image: if the image is a whole disk image or not.
+ """
+ if not is_whole_disk_image:
+ _replace_root_uuid(path, root_uuid_or_disk_id)
+ else:
+ _replace_disk_identifier(path, root_uuid_or_disk_id)
+
+ _replace_boot_line(path, boot_mode, is_whole_disk_image)
def notify(address, port):
@@ -480,10 +538,6 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format,
:param boot_option: Can be "local" or "netboot". "netboot" by default.
:returns: the UUID of the root partition.
"""
- if not is_block_device(dev):
- raise exception.InstanceDeployFailure(
- _("Parent device '%s' not found") % dev)
-
# the only way for preserve_ephemeral to be set to true is if we are
# rebuilding an instance with --preserve_ephemeral.
commit = not preserve_ephemeral
@@ -550,11 +604,11 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format,
return root_uuid
-def deploy(address, port, iqn, lun, image_path,
+def deploy_partition_image(address, port, iqn, lun, image_path,
root_mb, swap_mb, ephemeral_mb, ephemeral_format, node_uuid,
preserve_ephemeral=False, configdrive=None,
boot_option="netboot"):
- """All-in-one function to deploy a node.
+ """All-in-one function to deploy a partition image to a node.
:param address: The iSCSI IP address.
:param port: The iSCSI port number.
@@ -576,18 +630,60 @@ def deploy(address, port, iqn, lun, image_path,
:param boot_option: Can be "local" or "netboot". "netboot" by default.
:returns: the UUID of the root partition.
"""
- dev = get_dev(address, port, iqn, lun)
- image_mb = get_image_mb(image_path)
- if image_mb > root_mb:
- root_mb = image_mb
- discovery(address, port)
- login_iscsi(address, port, iqn)
- try:
+ with _iscsi_setup_and_handle_errors(address, port, iqn,
+ lun, image_path) as dev:
+ image_mb = get_image_mb(image_path)
+ if image_mb > root_mb:
+ root_mb = image_mb
+
root_uuid = work_on_disk(dev, root_mb, swap_mb, ephemeral_mb,
ephemeral_format, image_path, node_uuid,
preserve_ephemeral=preserve_ephemeral,
configdrive=configdrive,
boot_option=boot_option)
+
+ return root_uuid
+
+
+def deploy_disk_image(address, port, iqn, lun,
+ image_path, node_uuid):
+ """All-in-one function to deploy a whole disk image to a node.
+
+ :param address: The iSCSI IP address.
+ :param port: The iSCSI port number.
+ :param iqn: The iSCSI qualified name.
+ :param lun: The iSCSI logical unit number.
+ :param image_path: Path for the instance's disk image.
+ :param node_uuid: node's uuid. Used for logging. Currently not in use
+ by this function but could be used in the future.
+ """
+ with _iscsi_setup_and_handle_errors(address, port, iqn,
+ lun, image_path) as dev:
+ populate_image(image_path, dev)
+ disk_identifier = get_disk_identifier(dev)
+
+ return disk_identifier
+
+
+@contextlib.contextmanager
+def _iscsi_setup_and_handle_errors(address, port, iqn, lun,
+ image_path):
+ """Function that yields an iSCSI target device to work on.
+
+ :param address: The iSCSI IP address.
+ :param port: The iSCSI port number.
+ :param iqn: The iSCSI qualified name.
+ :param lun: The iSCSI logical unit number.
+ :param image_path: Path for the instance's disk image.
+ """
+ dev = get_dev(address, port, iqn, lun)
+ discovery(address, port)
+ login_iscsi(address, port, iqn)
+ if not is_block_device(dev):
+ raise exception.InstanceDeployFailure(_("Parent device '%s' not found")
+ % dev)
+ try:
+ yield dev
except processutils.ProcessExecutionError as err:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Deploy to address %s failed."), address)
@@ -602,8 +698,6 @@ def deploy(address, port, iqn, lun, image_path,
logout_iscsi(address, port, iqn)
delete_iscsi(address, port, iqn)
- return root_uuid
-
def notify_deploy_complete(address):
"""Notifies the completion of deployment to the baremetal node.
diff --git a/ironic/drivers/modules/elilo_efi_pxe_config.template b/ironic/drivers/modules/elilo_efi_pxe_config.template
index 18e46da51..71e680982 100644
--- a/ironic/drivers/modules/elilo_efi_pxe_config.template
+++ b/ironic/drivers/modules/elilo_efi_pxe_config.template
@@ -7,6 +7,10 @@ image={{pxe_options.deployment_aki_path}}
image={{pxe_options.aki_path}}
- label=boot
+ label=boot_partition
initrd={{pxe_options.ari_path}}
append="root={{ ROOT }} ro text {{ pxe_options.pxe_append_params|default("", true) }} ip=%I:{{pxe_options.tftp_server}}:%G:%M:%H::on"
+
+image=chain.c32
+ label=boot_whole_disk
+ append mbr:{{ DISK_IDENTIFIER }}
diff --git a/ironic/drivers/modules/ipxe_config.template b/ironic/drivers/modules/ipxe_config.template
index 8a4bc2354..bd5647841 100644
--- a/ironic/drivers/modules/ipxe_config.template
+++ b/ironic/drivers/modules/ipxe_config.template
@@ -10,7 +10,12 @@ kernel {{ pxe_options.deployment_aki_path }} selinux=0 disk={{ pxe_options.disk
initrd {{ pxe_options.deployment_ari_path }}
boot
-:boot
+:boot_partition
kernel {{ pxe_options.aki_path }} root={{ ROOT }} ro text {{ pxe_options.pxe_append_params|default("", true) }}
initrd {{ pxe_options.ari_path }}
boot
+
+:boot_whole_disk
+kernel chain.c32
+append mbr:{{ DISK_IDENTIFIER }}
+boot
diff --git a/ironic/drivers/modules/iscsi_deploy.py b/ironic/drivers/modules/iscsi_deploy.py
index e9070f55d..b93357528 100644
--- a/ironic/drivers/modules/iscsi_deploy.py
+++ b/ironic/drivers/modules/iscsi_deploy.py
@@ -114,10 +114,12 @@ def parse_instance_info(node):
info = node.instance_info
i_info = {}
i_info['image_source'] = info.get('image_source')
- if (i_info['image_source'] and
+ is_whole_disk_image = node.driver_internal_info.get('is_whole_disk_image')
+ if not is_whole_disk_image:
+ if (i_info['image_source'] and
not glance_service_utils.is_glance_image(i_info['image_source'])):
- i_info['kernel'] = info.get('kernel')
- i_info['ramdisk'] = info.get('ramdisk')
+ i_info['kernel'] = info.get('kernel')
+ i_info['ramdisk'] = info.get('ramdisk')
i_info['root_gb'] = info.get('root_gb')
error_msg = _("Cannot validate iSCSI deploy. Some parameters were missing"
@@ -129,18 +131,26 @@ def parse_instance_info(node):
i_info['swap_mb'] = info.get('swap_mb', 0)
i_info['ephemeral_gb'] = info.get('ephemeral_gb', 0)
- i_info['ephemeral_format'] = info.get('ephemeral_format')
- i_info['configdrive'] = info.get('configdrive')
-
err_msg_invalid = _("Cannot validate parameter for iSCSI deploy. "
"Invalid parameter %(param)s. Reason: %(reason)s")
for param in ('root_gb', 'swap_mb', 'ephemeral_gb'):
try:
int(i_info[param])
except ValueError:
- reason = _("'%s' is not an integer value.") % i_info[param]
+ reason = _("%s is not an integer value.") % i_info[param]
raise exception.InvalidParameterValue(err_msg_invalid %
- {'param': param, 'reason': reason})
+ {'param': param,
+ 'reason': reason})
+
+ if is_whole_disk_image:
+ if int(i_info['swap_mb']) > 0 or int(i_info['ephemeral_gb']) > 0:
+ err_msg_invalid = _("Cannot deploy whole disk image with "
+ "swap or ephemeral size set")
+ raise exception.InvalidParameterValue(err_msg_invalid)
+ return i_info
+
+ i_info['ephemeral_format'] = info.get('ephemeral_format')
+ i_info['configdrive'] = info.get('configdrive')
if i_info['ephemeral_gb'] and not i_info['ephemeral_format']:
i_info['ephemeral_format'] = CONF.pxe.default_ephemeral_format
@@ -228,12 +238,15 @@ def get_deploy_info(node, **kwargs):
'iqn': kwargs.get('iqn'),
'lun': kwargs.get('lun', '1'),
'image_path': _get_image_file_path(node.uuid),
- 'root_mb': 1024 * int(i_info['root_gb']),
- 'swap_mb': int(i_info['swap_mb']),
- 'ephemeral_mb': 1024 * int(i_info['ephemeral_gb']),
- 'preserve_ephemeral': i_info['preserve_ephemeral'],
- 'node_uuid': node.uuid,
- 'boot_option': get_boot_option(node)}
+ 'node_uuid': node.uuid}
+
+ is_whole_disk_image = node.driver_internal_info['is_whole_disk_image']
+ if not is_whole_disk_image:
+ params.update({'root_mb': 1024 * int(i_info['root_gb']),
+ 'swap_mb': int(i_info['swap_mb']),
+ 'ephemeral_mb': 1024 * int(i_info['ephemeral_gb']),
+ 'preserve_ephemeral': i_info['preserve_ephemeral'],
+ 'boot_option': get_boot_option(node)})
missing = [key for key in params if params[key] is None]
if missing:
@@ -241,6 +254,9 @@ def get_deploy_info(node, **kwargs):
"Parameters %s were not passed to ironic"
" for deploy.") % missing)
+ if is_whole_disk_image:
+ return params
+
# configdrive and ephemeral_format are nullable
params['ephemeral_format'] = i_info.get('ephemeral_format')
params['configdrive'] = i_info.get('configdrive')
@@ -258,7 +274,8 @@ def continue_deploy(task, **kwargs):
:param kwargs: the kwargs to be passed to deploy.
:raises: InvalidState if the event is not allowed by the associated
state machine.
- :returns: UUID of the root partition or None on error.
+ :returns: UUID of the root partition for partition images or disk
+ identifier for whole disk images or None on error.
"""
node = task.node
@@ -283,9 +300,13 @@ def continue_deploy(task, **kwargs):
LOG.debug('Continuing deployment for node %(node)s, params %(params)s',
{'node': node.uuid, 'params': log_params})
- root_uuid = None
+ root_uuid_or_disk_id = None
try:
- root_uuid = deploy_utils.deploy(**params)
+ if node.driver_internal_info['is_whole_disk_image']:
+ root_uuid_or_disk_id = deploy_utils.deploy_disk_image(**params)
+ else:
+ root_uuid_or_disk_id = deploy_utils.deploy_partition_image(
+ **params)
except Exception as e:
LOG.error(_LE('Deploy failed for instance %(instance)s. '
'Error: %(error)s'),
@@ -294,7 +315,7 @@ def continue_deploy(task, **kwargs):
'iSCSI deployment.'))
destroy_images(node.uuid)
- return root_uuid
+ return root_uuid_or_disk_id
def do_agent_iscsi_deploy(task, agent_client):
@@ -337,21 +358,22 @@ def do_agent_iscsi_deploy(task, agent_client):
'key': iscsi_options['deployment_key'],
'address': address}
- root_uuid = continue_deploy(task, **iscsi_params)
- if not root_uuid:
- msg = (_("Couldn't determine the UUID of the root partition "
- "when deploying node %s") % node.uuid)
+ root_uuid_or_disk_id = continue_deploy(task, **iscsi_params)
+ if not root_uuid_or_disk_id:
+ msg = (_("Couldn't determine the UUID of the root "
+ "partition or the disk identifier when deploying "
+ "node %s") % node.uuid)
deploy_utils.set_failed_state(task, msg)
raise exception.InstanceDeployFailure(reason=msg)
# TODO(lucasagomes): Move this bit saving the root_uuid to
# iscsi_deploy.continue_deploy()
driver_internal_info = node.driver_internal_info
- driver_internal_info['root_uuid'] = root_uuid
+ driver_internal_info['root_uuid_or_disk_id'] = root_uuid_or_disk_id
node.driver_internal_info = driver_internal_info
node.save()
- return root_uuid
+ return root_uuid_or_disk_id
def parse_root_device_hints(node):
diff --git a/ironic/drivers/modules/pxe.py b/ironic/drivers/modules/pxe.py
index 12bb34769..4d5d836a4 100644
--- a/ironic/drivers/modules/pxe.py
+++ b/ironic/drivers/modules/pxe.py
@@ -185,28 +185,34 @@ def _build_pxe_config_options(node, pxe_info, ctx):
:returns: A dictionary of pxe options to be used in the pxe bootfile
template.
"""
+ is_whole_disk_image = node.driver_internal_info['is_whole_disk_image']
+
if CONF.pxe.ipxe_enabled:
deploy_kernel = '/'.join([CONF.pxe.http_url, node.uuid,
'deploy_kernel'])
deploy_ramdisk = '/'.join([CONF.pxe.http_url, node.uuid,
'deploy_ramdisk'])
- kernel = '/'.join([CONF.pxe.http_url, node.uuid, 'kernel'])
- ramdisk = '/'.join([CONF.pxe.http_url, node.uuid, 'ramdisk'])
+ if not is_whole_disk_image:
+ kernel = '/'.join([CONF.pxe.http_url, node.uuid, 'kernel'])
+ ramdisk = '/'.join([CONF.pxe.http_url, node.uuid, 'ramdisk'])
else:
deploy_kernel = pxe_info['deploy_kernel'][1]
deploy_ramdisk = pxe_info['deploy_ramdisk'][1]
- kernel = pxe_info['kernel'][1]
- ramdisk = pxe_info['ramdisk'][1]
+ if not is_whole_disk_image:
+ kernel = pxe_info['kernel'][1]
+ ramdisk = pxe_info['ramdisk'][1]
pxe_options = {
'deployment_aki_path': deploy_kernel,
'deployment_ari_path': deploy_ramdisk,
- 'aki_path': kernel,
- 'ari_path': ramdisk,
'pxe_append_params': CONF.pxe.pxe_append_params,
'tftp_server': CONF.pxe.tftp_server
}
+ if not is_whole_disk_image:
+ pxe_options.update({'aki_path': kernel,
+ 'ari_path': ramdisk})
+
deploy_ramdisk_options = iscsi_deploy.build_deploy_ramdisk_options(node)
pxe_options.update(deploy_ramdisk_options)
@@ -240,7 +246,7 @@ def _cache_ramdisk_kernel(ctx, node, pxe_info):
"""Fetch the necessary kernels and ramdisks for the instance."""
fileutils.ensure_tree(
os.path.join(pxe_utils.get_root_dir(), node.uuid))
- LOG.debug("Fetching kernel and ramdisk for node %s",
+ LOG.debug("Fetching necessary kernel and ramdisk for node %s",
node.uuid)
deploy_utils.fetch_images(ctx, TFTPImageCache(), pxe_info.values(),
CONF.force_raw_images)
@@ -261,6 +267,9 @@ def _get_image_info(node, ctx):
image_info.update(pxe_utils.get_deploy_kr_info(node.uuid, d_info))
+ if node.driver_internal_info['is_whole_disk_image']:
+ return image_info
+
i_info = node.instance_info
labels = ('kernel', 'ramdisk')
if not (i_info.get('kernel') and i_info.get('ramdisk')):
@@ -349,10 +358,13 @@ class PXEDeploy(base.DeployInterface):
iscsi_deploy.validate(task)
- if service_utils.is_glance_image(d_info['image_source']):
+ if node.driver_internal_info.get('is_whole_disk_image'):
+ props = []
+ elif service_utils.is_glance_image(d_info['image_source']):
props = ['kernel_id', 'ramdisk_id']
else:
props = ['kernel', 'ramdisk']
+
iscsi_deploy.validate_image_properties(task.context, d_info, props)
@task_manager.require_exclusive_lock
@@ -428,25 +440,34 @@ class PXEDeploy(base.DeployInterface):
# the image kernel and ramdisk (Or even require it).
_cache_ramdisk_kernel(task.context, task.node, pxe_info)
+ iwdi = task.node.driver_internal_info['is_whole_disk_image']
# NOTE(deva): prepare may be called from conductor._do_takeover
# in which case, it may need to regenerate the PXE config file for an
# already-active deployment.
if task.node.provision_state == states.ACTIVE:
+ # this should have been stashed when the deploy was done
+ # but let's guard, just in case it's missing
try:
- # this should have been stashed when the deploy was done
- # but let's guard, just in case it's missing
- root_uuid = task.node.driver_internal_info['root_uuid']
+ root_uuid_or_disk_id = task.node.driver_internal_info[
+ 'root_uuid_or_disk_id']
except KeyError:
- LOG.warn(_LW("The UUID for the root partition can't be found, "
- "unable to switch the pxe config from deployment "
- "mode to service (boot) mode for node %(node)s"),
- {"node": task.node.uuid})
+ if not iwdi:
+ LOG.warn(_LW("The UUID for the root partition can't be "
+ "found, unable to switch the pxe config from "
+ "deployment mode to service (boot) mode for node "
+ "%(node)s"), {"node": task.node.uuid})
+ else:
+ LOG.warn(_LW("The disk id for the whole disk image can't "
+ "be found, unable to switch the pxe config from "
+ "deployment mode to service (boot) mode for "
+ "node %(node)s"), {"node": task.node.uuid})
else:
pxe_config_path = pxe_utils.get_pxe_config_file_path(
task.node.uuid)
deploy_utils.switch_pxe_config(
- pxe_config_path, root_uuid,
- driver_utils.get_node_capability(task.node, 'boot_mode'))
+ pxe_config_path, root_uuid_or_disk_id,
+ driver_utils.get_node_capability(task.node, 'boot_mode'),
+ iwdi)
def clean_up(self, task):
"""Clean up the deployment environment for the task's node.
@@ -529,10 +550,9 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
task.process_event('resume')
_destroy_token_file(node)
-
- root_uuid = iscsi_deploy.continue_deploy(task, **kwargs)
-
- if not root_uuid:
+ is_whole_disk_image = node.driver_internal_info['is_whole_disk_image']
+ root_uuid_or_disk_id = iscsi_deploy.continue_deploy(task, **kwargs)
+ if not root_uuid_or_disk_id:
return
# save the node's root disk UUID so that another conductor could
@@ -540,7 +560,7 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
# we have to assign to node.driver_internal_info so the node knows it
# has changed.
driver_internal_info = node.driver_internal_info
- driver_internal_info['root_uuid'] = root_uuid
+ driver_internal_info['root_uuid_or_disk_id'] = root_uuid_or_disk_id
node.driver_internal_info = driver_internal_info
node.save()
@@ -552,8 +572,10 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
pxe_utils.clean_up_pxe_config(task)
else:
pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid)
- deploy_utils.switch_pxe_config(pxe_config_path, root_uuid,
- driver_utils.get_node_capability(node, 'boot_mode'))
+ node_cap = driver_utils.get_node_capability(node, 'boot_mode')
+ deploy_utils.switch_pxe_config(pxe_config_path,
+ root_uuid_or_disk_id,
+ node_cap, is_whole_disk_image)
deploy_utils.notify_deploy_complete(kwargs['address'])
LOG.info(_LI('Deployment to node %s done'), node.uuid)
@@ -588,11 +610,13 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
# it here.
_destroy_token_file(node)
- root_uuid = iscsi_deploy.do_agent_iscsi_deploy(task, self._client)
-
+ root_uuid_or_disk_id = iscsi_deploy.do_agent_iscsi_deploy(
+ task,
+ self._client)
+ is_whole_disk_image = node.driver_internal_info['is_whole_disk_image']
if iscsi_deploy.get_boot_option(node) == "local":
# Install the boot loader
- self.configure_local_boot(task, root_uuid)
+ self.configure_local_boot(task, root_uuid_or_disk_id)
# If it's going to boot from the local disk, get rid of
# the PXE configuration files used for the deployment
@@ -600,7 +624,9 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
else:
pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid)
boot_mode = driver_utils.get_node_capability(node, 'boot_mode')
- deploy_utils.switch_pxe_config(pxe_config_path, root_uuid,
- boot_mode)
+ deploy_utils.switch_pxe_config(
+ pxe_config_path,
+ root_uuid_or_disk_id,
+ boot_mode, is_whole_disk_image)
self.reboot_and_finish_deploy(task)
diff --git a/ironic/drivers/modules/pxe_config.template b/ironic/drivers/modules/pxe_config.template
index 9846374a5..d231ca80b 100644
--- a/ironic/drivers/modules/pxe_config.template
+++ b/ironic/drivers/modules/pxe_config.template
@@ -6,6 +6,11 @@ append initrd={{ pxe_options.deployment_ari_path }} selinux=0 disk={{ pxe_option
ipappend 3
-label boot
+label boot_partition
kernel {{ pxe_options.aki_path }}
append initrd={{ pxe_options.ari_path }} root={{ ROOT }} ro text {{ pxe_options.pxe_append_params|default("", true) }}
+
+
+label boot_whole_disk
+COM32 chain.c32
+append mbr:{{ DISK_IDENTIFIER }}
diff --git a/ironic/tests/conductor/test_manager.py b/ironic/tests/conductor/test_manager.py
index f48ee2b68..6e656d960 100644
--- a/ironic/tests/conductor/test_manager.py
+++ b/ironic/tests/conductor/test_manager.py
@@ -31,6 +31,7 @@ from oslo_utils import uuidutils
from ironic.common import boot_devices
from ironic.common import driver_factory
from ironic.common import exception
+from ironic.common import images
from ironic.common import keystone
from ironic.common import states
from ironic.common import swift
@@ -922,9 +923,11 @@ class VendorPassthruTestCase(_ServiceSetUpMixin, tests_db_base.DbTestCase):
@_mock_record_keepalive
-class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
+@mock.patch.object(images, 'is_whole_disk_image')
+class ServiceDoNodeDeployTestCase(_ServiceSetUpMixin,
tests_db_base.DbTestCase):
- def test_do_node_deploy_invalid_state(self):
+ def test_do_node_deploy_invalid_state(self, mock_iwdi):
+ mock_iwdi.return_value = False
self._start_service()
# test that node deploy fails if the node is already provisioned
node = obj_utils.create_test_node(self.context, driver='fake',
@@ -939,8 +942,11 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
+ mock_iwdi.assert_called_once_with(self.context, node.instance_info)
+ self.assertNotIn('is_whole_disk_image', node.driver_internal_info)
- def test_do_node_deploy_maintenance(self):
+ def test_do_node_deploy_maintenance(self, mock_iwdi):
+ mock_iwdi.return_value = False
node = obj_utils.create_test_node(self.context, driver='fake',
maintenance=True)
exc = self.assertRaises(messaging.rpc.ExpectedException,
@@ -952,8 +958,10 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
+ self.assertFalse(mock_iwdi.called)
- def _test_do_node_deploy_validate_fail(self, mock_validate):
+ def _test_do_node_deploy_validate_fail(self, mock_validate, mock_iwdi):
+ mock_iwdi.return_value = False
# InvalidParameterValue should be re-raised as InstanceDeployFailure
mock_validate.side_effect = exception.InvalidParameterValue('error')
node = obj_utils.create_test_node(self.context, driver='fake')
@@ -966,153 +974,28 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
+ mock_iwdi.assert_called_once_with(self.context, node.instance_info)
+ self.assertNotIn('is_whole_disk_image', node.driver_internal_info)
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.validate')
- def test_do_node_deploy_validate_fail(self, mock_validate):
- self._test_do_node_deploy_validate_fail(mock_validate)
+ def test_do_node_deploy_validate_fail(self, mock_validate, mock_iwdi):
+ self._test_do_node_deploy_validate_fail(mock_validate, mock_iwdi)
@mock.patch('ironic.drivers.modules.fake.FakePower.validate')
- def test_do_node_deploy_power_validate_fail(self, mock_validate):
- self._test_do_node_deploy_validate_fail(mock_validate)
-
- @mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
- @mock.patch('ironic.drivers.modules.fake.FakeDeploy.prepare')
- def test__do_node_deploy_driver_raises_prepare_error(self, mock_prepare,
- mock_deploy):
- self._start_service()
- # test when driver.deploy.prepare raises an exception
- mock_prepare.side_effect = exception.InstanceDeployFailure('test')
- node = obj_utils.create_test_node(self.context, driver='fake',
- provision_state=states.DEPLOYING,
- target_provision_state=states.ACTIVE)
- task = task_manager.TaskManager(self.context, node.uuid)
-
- self.assertRaises(exception.InstanceDeployFailure,
- manager.do_node_deploy, task,
- self.service.conductor.id)
- node.refresh()
- self.assertEqual(states.DEPLOYFAIL, node.provision_state)
- # NOTE(deva): failing a deploy does not clear the target state
- # any longer. Instead, it is cleared when the instance
- # is deleted.
- self.assertEqual(states.ACTIVE, node.target_provision_state)
- self.assertIsNotNone(node.last_error)
- self.assertTrue(mock_prepare.called)
- self.assertFalse(mock_deploy.called)
-
- @mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
- def test__do_node_deploy_driver_raises_error(self, mock_deploy):
- self._start_service()
- # test when driver.deploy.deploy raises an exception
- mock_deploy.side_effect = exception.InstanceDeployFailure('test')
- node = obj_utils.create_test_node(self.context, driver='fake',
- provision_state=states.DEPLOYING,
- target_provision_state=states.ACTIVE)
- task = task_manager.TaskManager(self.context, node.uuid)
-
- self.assertRaises(exception.InstanceDeployFailure,
- manager.do_node_deploy, task,
- self.service.conductor.id)
- node.refresh()
- self.assertEqual(states.DEPLOYFAIL, node.provision_state)
- # NOTE(deva): failing a deploy does not clear the target state
- # any longer. Instead, it is cleared when the instance
- # is deleted.
- self.assertEqual(states.ACTIVE, node.target_provision_state)
- self.assertIsNotNone(node.last_error)
- mock_deploy.assert_called_once_with(mock.ANY)
-
- @mock.patch.object(manager, '_store_configdrive')
- @mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
- def test__do_node_deploy_ok(self, mock_deploy, mock_store):
- self._start_service()
- # test when driver.deploy.deploy returns DEPLOYDONE
- mock_deploy.return_value = states.DEPLOYDONE
- node = obj_utils.create_test_node(self.context, driver='fake',
- provision_state=states.DEPLOYING,
- target_provision_state=states.ACTIVE)
- task = task_manager.TaskManager(self.context, node.uuid)
-
- manager.do_node_deploy(task, self.service.conductor.id)
- node.refresh()
- self.assertEqual(states.ACTIVE, node.provision_state)
- self.assertEqual(states.NOSTATE, node.target_provision_state)
- self.assertIsNone(node.last_error)
- mock_deploy.assert_called_once_with(mock.ANY)
- # assert _store_configdrive wasn't invoked
- self.assertFalse(mock_store.called)
-
- @mock.patch.object(manager, '_store_configdrive')
- @mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
- def test__do_node_deploy_ok_configdrive(self, mock_deploy, mock_store):
- self._start_service()
- # test when driver.deploy.deploy returns DEPLOYDONE
- mock_deploy.return_value = states.DEPLOYDONE
- node = obj_utils.create_test_node(self.context, driver='fake',
- provision_state=states.DEPLOYING,
- target_provision_state=states.ACTIVE)
- task = task_manager.TaskManager(self.context, node.uuid)
- configdrive = 'foo'
-
- manager.do_node_deploy(task, self.service.conductor.id,
- configdrive=configdrive)
- node.refresh()
- self.assertEqual(states.ACTIVE, node.provision_state)
- self.assertEqual(states.NOSTATE, node.target_provision_state)
- self.assertIsNone(node.last_error)
- mock_deploy.assert_called_once_with(mock.ANY)
- mock_store.assert_called_once_with(task.node, configdrive)
-
- @mock.patch.object(swift, 'SwiftAPI')
- @mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
- def test__do_node_deploy_configdrive_swift_error(self, mock_deploy,
- mock_swift):
- CONF.set_override('configdrive_use_swift', True, group='conductor')
- self._start_service()
- # test when driver.deploy.deploy returns DEPLOYDONE
- mock_deploy.return_value = states.DEPLOYDONE
- node = obj_utils.create_test_node(self.context, driver='fake',
- provision_state=states.DEPLOYING,
- target_provision_state=states.ACTIVE)
- task = task_manager.TaskManager(self.context, node.uuid)
-
- mock_swift.side_effect = exception.SwiftOperationError('error')
- self.assertRaises(exception.SwiftOperationError,
- manager.do_node_deploy, task,
- self.service.conductor.id,
- configdrive='fake config drive')
- node.refresh()
- self.assertEqual(states.DEPLOYFAIL, node.provision_state)
- self.assertEqual(states.ACTIVE, node.target_provision_state)
- self.assertIsNotNone(node.last_error)
- self.assertFalse(mock_deploy.called)
-
- @mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
- def test__do_node_deploy_ok_2(self, mock_deploy):
- # NOTE(rloo): a different way of testing for the same thing as in
- # test__do_node_deploy_ok()
- self._start_service()
- # test when driver.deploy.deploy returns DEPLOYDONE
- mock_deploy.return_value = states.DEPLOYDONE
- node = obj_utils.create_test_node(self.context, driver='fake')
- task = task_manager.TaskManager(self.context, node.uuid)
- task.process_event('deploy')
-
- manager.do_node_deploy(task, self.service.conductor.id)
- node.refresh()
- self.assertEqual(states.ACTIVE, node.provision_state)
- self.assertEqual(states.NOSTATE, node.target_provision_state)
- self.assertIsNone(node.last_error)
- mock_deploy.assert_called_once_with(mock.ANY)
+ def test_do_node_deploy_power_validate_fail(self, mock_validate,
+ mock_iwdi):
+ self._test_do_node_deploy_validate_fail(mock_validate, mock_iwdi)
@mock.patch('ironic.conductor.task_manager.TaskManager.process_event')
- def test_deploy_with_nostate_converts_to_available(self, mock_pe):
+ def test_deploy_with_nostate_converts_to_available(self, mock_pe,
+ mock_iwdi):
# expressly create a node using the Juno-era NOSTATE state
# and assert that it does not result in an error, and that the state
# is converted to the new AVAILABLE state.
# Mock the process_event call, because the transitions from
# AVAILABLE are tested thoroughly elsewhere
# NOTE(deva): This test can be deleted after Kilo is released
+ mock_iwdi.return_value = False
self._start_service()
node = obj_utils.create_test_node(self.context, driver='fake',
provision_state=states.NOSTATE)
@@ -1121,8 +1004,11 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
self.assertTrue(mock_pe.called)
node.refresh()
self.assertEqual(states.AVAILABLE, node.provision_state)
+ mock_iwdi.assert_called_once_with(self.context, node.instance_info)
+ self.assertFalse(node.driver_internal_info['is_whole_disk_image'])
- def test_do_node_deploy_partial_ok(self):
+ def test_do_node_deploy_partial_ok(self, mock_iwdi):
+ mock_iwdi.return_value = False
self._start_service()
thread = self.service._spawn_worker(lambda: None)
with mock.patch.object(self.service, '_spawn_worker') as mock_spawn:
@@ -1142,19 +1028,23 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
self.assertIsNone(node.reservation)
mock_spawn.assert_called_once_with(mock.ANY, mock.ANY,
mock.ANY, None)
+ mock_iwdi.assert_called_once_with(self.context, node.instance_info)
+ self.assertFalse(node.driver_internal_info['is_whole_disk_image'])
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
- def test_do_node_deploy_rebuild_active_state(self, mock_deploy):
+ def test_do_node_deploy_rebuild_active_state(self, mock_deploy, mock_iwdi):
# This tests manager.do_node_deploy(), the 'else' path of
# 'if new_state == states.DEPLOYDONE'. The node's states
# aren't changed in this case.
+ mock_iwdi.return_value = True
self._start_service()
mock_deploy.return_value = states.DEPLOYING
node = obj_utils.create_test_node(self.context, driver='fake',
provision_state=states.ACTIVE,
target_provision_state=states.NOSTATE,
instance_info={'image_source': uuidutils.generate_uuid(),
- 'kernel': 'aaaa', 'ramdisk': 'bbbb'})
+ 'kernel': 'aaaa', 'ramdisk': 'bbbb'},
+ driver_internal_info={'is_whole_disk_image': False})
self.service.do_node_deploy(self.context, node.uuid, rebuild=True)
self.service._worker_pool.waitall()
@@ -1169,9 +1059,14 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
# Verify instance_info values has been cleared.
self.assertNotIn('kernel', node.instance_info)
self.assertNotIn('ramdisk', node.instance_info)
+ mock_iwdi.assert_called_once_with(self.context, node.instance_info)
+ # Verify is_whole_disk_image reflects correct value on rebuild.
+ self.assertTrue(node.driver_internal_info['is_whole_disk_image'])
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
- def test_do_node_deploy_rebuild_active_state_waiting(self, mock_deploy):
+ def test_do_node_deploy_rebuild_active_state_waiting(self, mock_deploy,
+ mock_iwdi):
+ mock_iwdi.return_value = False
self._start_service()
mock_deploy.return_value = states.DEPLOYWAIT
node = obj_utils.create_test_node(self.context, driver='fake',
@@ -1189,9 +1084,13 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_deploy.assert_called_once_with(mock.ANY)
+ mock_iwdi.assert_called_once_with(self.context, node.instance_info)
+ self.assertFalse(node.driver_internal_info['is_whole_disk_image'])
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
- def test_do_node_deploy_rebuild_active_state_done(self, mock_deploy):
+ def test_do_node_deploy_rebuild_active_state_done(self, mock_deploy,
+ mock_iwdi):
+ mock_iwdi.return_value = False
self._start_service()
mock_deploy.return_value = states.DEPLOYDONE
node = obj_utils.create_test_node(self.context, driver='fake',
@@ -1208,9 +1107,13 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_deploy.assert_called_once_with(mock.ANY)
+ mock_iwdi.assert_called_once_with(self.context, node.instance_info)
+ self.assertFalse(node.driver_internal_info['is_whole_disk_image'])
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
- def test_do_node_deploy_rebuild_deployfail_state(self, mock_deploy):
+ def test_do_node_deploy_rebuild_deployfail_state(self, mock_deploy,
+ mock_iwdi):
+ mock_iwdi.return_value = False
self._start_service()
mock_deploy.return_value = states.DEPLOYDONE
node = obj_utils.create_test_node(self.context, driver='fake',
@@ -1227,9 +1130,12 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_deploy.assert_called_once_with(mock.ANY)
+ mock_iwdi.assert_called_once_with(self.context, node.instance_info)
+ self.assertFalse(node.driver_internal_info['is_whole_disk_image'])
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
- def test_do_node_deploy_rebuild_error_state(self, mock_deploy):
+ def test_do_node_deploy_rebuild_error_state(self, mock_deploy, mock_iwdi):
+ mock_iwdi.return_value = False
self._start_service()
mock_deploy.return_value = states.DEPLOYDONE
node = obj_utils.create_test_node(self.context, driver='fake',
@@ -1246,8 +1152,11 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
mock_deploy.assert_called_once_with(mock.ANY)
+ mock_iwdi.assert_called_once_with(self.context, node.instance_info)
+ self.assertFalse(node.driver_internal_info['is_whole_disk_image'])
- def test_do_node_deploy_rebuild_from_available_state(self):
+ def test_do_node_deploy_rebuild_from_available_state(self, mock_iwdi):
+ mock_iwdi.return_value = False
self._start_service()
# test node will not rebuild if state is AVAILABLE
node = obj_utils.create_test_node(self.context, driver='fake',
@@ -1261,25 +1170,11 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
self.assertIsNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
+ mock_iwdi.assert_called_once_with(self.context, node.instance_info)
+ self.assertNotIn('is_whole_disk_image', node.driver_internal_info)
- @mock.patch('ironic.drivers.modules.fake.FakeDeploy.clean_up')
- def test__check_deploy_timeouts(self, mock_cleanup):
- self._start_service()
- CONF.set_override('deploy_callback_timeout', 1, group='conductor')
- node = obj_utils.create_test_node(self.context, driver='fake',
- provision_state=states.DEPLOYWAIT,
- target_provision_state=states.ACTIVE,
- provision_updated_at=datetime.datetime(2000, 1, 1, 0, 0))
-
- self.service._check_deploy_timeouts(self.context)
- self.service._worker_pool.waitall()
- node.refresh()
- self.assertEqual(states.DEPLOYFAIL, node.provision_state)
- self.assertEqual(states.ACTIVE, node.target_provision_state)
- self.assertIsNotNone(node.last_error)
- mock_cleanup.assert_called_once_with(mock.ANY)
-
- def test_do_node_deploy_worker_pool_full(self):
+ def test_do_node_deploy_worker_pool_full(self, mock_iwdi):
+ mock_iwdi.return_value = False
prv_state = states.AVAILABLE
tgt_prv_state = states.NOSTATE
node = obj_utils.create_test_node(self.context,
@@ -1304,6 +1199,159 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
self.assertIsNotNone(node.last_error)
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
+ mock_iwdi.assert_called_once_with(self.context, node.instance_info)
+ self.assertFalse(node.driver_internal_info['is_whole_disk_image'])
+
+
+@_mock_record_keepalive
+class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
+ tests_db_base.DbTestCase):
+ @mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
+ @mock.patch('ironic.drivers.modules.fake.FakeDeploy.prepare')
+ def test__do_node_deploy_driver_raises_prepare_error(self, mock_prepare,
+ mock_deploy):
+ self._start_service()
+ # test when driver.deploy.prepare raises an exception
+ mock_prepare.side_effect = exception.InstanceDeployFailure('test')
+ node = obj_utils.create_test_node(self.context, driver='fake',
+ provision_state=states.DEPLOYING,
+ target_provision_state=states.ACTIVE)
+ task = task_manager.TaskManager(self.context, node.uuid)
+
+ self.assertRaises(exception.InstanceDeployFailure,
+ manager.do_node_deploy, task,
+ self.service.conductor.id)
+ node.refresh()
+ self.assertEqual(states.DEPLOYFAIL, node.provision_state)
+ # NOTE(deva): failing a deploy does not clear the target state
+ # any longer. Instead, it is cleared when the instance
+ # is deleted.
+ self.assertEqual(states.ACTIVE, node.target_provision_state)
+ self.assertIsNotNone(node.last_error)
+ self.assertTrue(mock_prepare.called)
+ self.assertFalse(mock_deploy.called)
+
+ @mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
+ def test__do_node_deploy_driver_raises_error(self, mock_deploy):
+ self._start_service()
+ # test when driver.deploy.deploy raises an exception
+ mock_deploy.side_effect = exception.InstanceDeployFailure('test')
+ node = obj_utils.create_test_node(self.context, driver='fake',
+ provision_state=states.DEPLOYING,
+ target_provision_state=states.ACTIVE)
+ task = task_manager.TaskManager(self.context, node.uuid)
+
+ self.assertRaises(exception.InstanceDeployFailure,
+ manager.do_node_deploy, task,
+ self.service.conductor.id)
+ node.refresh()
+ self.assertEqual(states.DEPLOYFAIL, node.provision_state)
+ # NOTE(deva): failing a deploy does not clear the target state
+ # any longer. Instead, it is cleared when the instance
+ # is deleted.
+ self.assertEqual(states.ACTIVE, node.target_provision_state)
+ self.assertIsNotNone(node.last_error)
+ mock_deploy.assert_called_once_with(mock.ANY)
+
+ @mock.patch.object(manager, '_store_configdrive')
+ @mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
+ def test__do_node_deploy_ok(self, mock_deploy, mock_store):
+ self._start_service()
+ # test when driver.deploy.deploy returns DEPLOYDONE
+ mock_deploy.return_value = states.DEPLOYDONE
+ node = obj_utils.create_test_node(self.context, driver='fake',
+ provision_state=states.DEPLOYING,
+ target_provision_state=states.ACTIVE)
+ task = task_manager.TaskManager(self.context, node.uuid)
+
+ manager.do_node_deploy(task, self.service.conductor.id)
+ node.refresh()
+ self.assertEqual(states.ACTIVE, node.provision_state)
+ self.assertEqual(states.NOSTATE, node.target_provision_state)
+ self.assertIsNone(node.last_error)
+ mock_deploy.assert_called_once_with(mock.ANY)
+ # assert _store_configdrive wasn't invoked
+ self.assertFalse(mock_store.called)
+
+ @mock.patch.object(manager, '_store_configdrive')
+ @mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
+ def test__do_node_deploy_ok_configdrive(self, mock_deploy, mock_store):
+ self._start_service()
+ # test when driver.deploy.deploy returns DEPLOYDONE
+ mock_deploy.return_value = states.DEPLOYDONE
+ node = obj_utils.create_test_node(self.context, driver='fake',
+ provision_state=states.DEPLOYING,
+ target_provision_state=states.ACTIVE)
+ task = task_manager.TaskManager(self.context, node.uuid)
+ configdrive = 'foo'
+
+ manager.do_node_deploy(task, self.service.conductor.id,
+ configdrive=configdrive)
+ node.refresh()
+ self.assertEqual(states.ACTIVE, node.provision_state)
+ self.assertEqual(states.NOSTATE, node.target_provision_state)
+ self.assertIsNone(node.last_error)
+ mock_deploy.assert_called_once_with(mock.ANY)
+ mock_store.assert_called_once_with(task.node, configdrive)
+
+ @mock.patch.object(swift, 'SwiftAPI')
+ @mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
+ def test__do_node_deploy_configdrive_swift_error(self, mock_deploy,
+ mock_swift):
+ CONF.set_override('configdrive_use_swift', True, group='conductor')
+ self._start_service()
+ # test when driver.deploy.deploy returns DEPLOYDONE
+ mock_deploy.return_value = states.DEPLOYDONE
+ node = obj_utils.create_test_node(self.context, driver='fake',
+ provision_state=states.DEPLOYING,
+ target_provision_state=states.ACTIVE)
+ task = task_manager.TaskManager(self.context, node.uuid)
+
+ mock_swift.side_effect = exception.SwiftOperationError('error')
+ self.assertRaises(exception.SwiftOperationError,
+ manager.do_node_deploy, task,
+ self.service.conductor.id,
+ configdrive='fake config drive')
+ node.refresh()
+ self.assertEqual(states.DEPLOYFAIL, node.provision_state)
+ self.assertEqual(states.ACTIVE, node.target_provision_state)
+ self.assertIsNotNone(node.last_error)
+ self.assertFalse(mock_deploy.called)
+
+ @mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
+ def test__do_node_deploy_ok_2(self, mock_deploy):
+ # NOTE(rloo): a different way of testing for the same thing as in
+ # test__do_node_deploy_ok()
+ self._start_service()
+ # test when driver.deploy.deploy returns DEPLOYDONE
+ mock_deploy.return_value = states.DEPLOYDONE
+ node = obj_utils.create_test_node(self.context, driver='fake')
+ task = task_manager.TaskManager(self.context, node.uuid)
+ task.process_event('deploy')
+
+ manager.do_node_deploy(task, self.service.conductor.id)
+ node.refresh()
+ self.assertEqual(states.ACTIVE, node.provision_state)
+ self.assertEqual(states.NOSTATE, node.target_provision_state)
+ self.assertIsNone(node.last_error)
+ mock_deploy.assert_called_once_with(mock.ANY)
+
+ @mock.patch('ironic.drivers.modules.fake.FakeDeploy.clean_up')
+ def test__check_deploy_timeouts(self, mock_cleanup):
+ self._start_service()
+ CONF.set_override('deploy_callback_timeout', 1, group='conductor')
+ node = obj_utils.create_test_node(self.context, driver='fake',
+ provision_state=states.DEPLOYWAIT,
+ target_provision_state=states.ACTIVE,
+ provision_updated_at=datetime.datetime(2000, 1, 1, 0, 0))
+
+ self.service._check_deploy_timeouts(self.context)
+ self.service._worker_pool.waitall()
+ node.refresh()
+ self.assertEqual(states.DEPLOYFAIL, node.provision_state)
+ self.assertEqual(states.ACTIVE, node.target_provision_state)
+ self.assertIsNotNone(node.last_error)
+ mock_cleanup.assert_called_once_with(mock.ANY)
def test_do_node_tear_down_invalid_state(self):
self._start_service()
@@ -1332,10 +1380,11 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.tear_down')
def test_do_node_tear_down_driver_raises_error(self, mock_tear_down):
# test when driver.deploy.tear_down raises exception
- node = obj_utils.create_test_node(self.context, driver='fake',
- provision_state=states.DELETING,
- target_provision_state=states.AVAILABLE,
- instance_info={'foo': 'bar'})
+ node = obj_utils.create_test_node(
+ self.context, driver='fake', provision_state=states.DELETING,
+ target_provision_state=states.AVAILABLE,
+ instance_info={'foo': 'bar'},
+ driver_internal_info={'is_whole_disk_image': False})
task = task_manager.TaskManager(self.context, node.uuid)
self._start_service()
@@ -1354,10 +1403,11 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.tear_down')
def test__do_node_tear_down_ok(self, mock_tear_down, mock_clean):
# test when driver.deploy.tear_down succeeds
- node = obj_utils.create_test_node(self.context, driver='fake',
- provision_state=states.DELETING,
- target_provision_state=states.AVAILABLE,
- instance_info={'foo': 'bar'})
+ node = obj_utils.create_test_node(
+ self.context, driver='fake', provision_state=states.DELETING,
+ target_provision_state=states.AVAILABLE,
+ instance_info={'foo': 'bar'},
+ driver_internal_info={'is_whole_disk_image': False})
task = task_manager.TaskManager(self.context, node.uuid)
self._start_service()
@@ -1375,10 +1425,11 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.tear_down')
def _test_do_node_tear_down_from_state(self, init_state, mock_tear_down,
mock_clean):
- node = obj_utils.create_test_node(self.context, driver='fake',
- uuid=uuidutils.generate_uuid(),
- provision_state=init_state,
- target_provision_state=states.AVAILABLE)
+ node = obj_utils.create_test_node(
+ self.context, driver='fake', uuid=uuidutils.generate_uuid(),
+ provision_state=init_state,
+ target_provision_state=states.AVAILABLE,
+ driver_internal_info={'is_whole_disk_image': False})
self.service.do_node_tear_down(self.context, node.uuid)
self.service._worker_pool.waitall()
@@ -1410,11 +1461,12 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
prv_state = states.ACTIVE
tgt_prv_state = states.NOSTATE
fake_instance_info = {'foo': 'bar'}
- node = obj_utils.create_test_node(self.context, driver='fake',
- provision_state=prv_state,
- target_provision_state=tgt_prv_state,
- instance_info=fake_instance_info,
- last_error=None)
+ driver_internal_info = {'is_whole_disk_image': False}
+ node = obj_utils.create_test_node(
+ self.context, driver='fake', provision_state=prv_state,
+ target_provision_state=tgt_prv_state,
+ instance_info=fake_instance_info,
+ driver_internal_info=driver_internal_info, last_error=None)
self._start_service()
mock_spawn.side_effect = exception.NoFreeConductorWorker()
@@ -1426,8 +1478,9 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
self.assertEqual(exception.NoFreeConductorWorker, exc.exc_info[0])
self.service._worker_pool.waitall()
node.refresh()
- # Assert instance_info was not touched
+ # Assert instance_info/driver_internal_info was not touched
self.assertEqual(fake_instance_info, node.instance_info)
+ self.assertEqual(driver_internal_info, node.driver_internal_info)
# Make sure things were rolled back
self.assertEqual(prv_state, node.provision_state)
self.assertEqual(tgt_prv_state, node.target_provision_state)
@@ -1511,9 +1564,12 @@ class MiscTestCase(_ServiceSetUpMixin, _CommonMixIn, tests_db_base.DbTestCase):
self.assertTrue(self.service._mapped_to_this_conductor(n['uuid'],
'fake'))
self.assertFalse(self.service._mapped_to_this_conductor(n['uuid'],
+
'otherdriver'))
- def test_validate_driver_interfaces(self):
+ @mock.patch.object(images, 'is_whole_disk_image')
+ def test_validate_driver_interfaces(self, mock_iwdi):
+ mock_iwdi.return_value = False
node = obj_utils.create_test_node(self.context, driver='fake')
ret = self.service.validate_driver_interfaces(self.context,
node.uuid)
@@ -1523,8 +1579,11 @@ class MiscTestCase(_ServiceSetUpMixin, _CommonMixIn, tests_db_base.DbTestCase):
'management': {'result': True},
'deploy': {'result': True}}
self.assertEqual(expected, ret)
+ mock_iwdi.assert_called_once_with(self.context, node.instance_info)
- def test_validate_driver_interfaces_validation_fail(self):
+ @mock.patch.object(images, 'is_whole_disk_image')
+ def test_validate_driver_interfaces_validation_fail(self, mock_iwdi):
+ mock_iwdi.return_value = False
node = obj_utils.create_test_node(self.context, driver='fake')
with mock.patch(
'ironic.drivers.modules.fake.FakeDeploy.validate'
@@ -1535,6 +1594,7 @@ class MiscTestCase(_ServiceSetUpMixin, _CommonMixIn, tests_db_base.DbTestCase):
node.uuid)
self.assertFalse(ret['deploy']['result'])
self.assertEqual(reason, ret['deploy']['reason'])
+ mock_iwdi.assert_called_once_with(self.context, node.instance_info)
@mock.patch.object(manager.ConductorManager, '_mapped_to_this_conductor')
@mock.patch.object(dbapi.IMPL, 'get_nodeinfo_list')
diff --git a/ironic/tests/db/utils.py b/ironic/tests/db/utils.py
index 0e5da57b1..4b91171f2 100644
--- a/ironic/tests/db/utils.py
+++ b/ironic/tests/db/utils.py
@@ -69,6 +69,12 @@ def get_test_pxe_driver_info():
}
+def get_test_pxe_driver_internal_info():
+ return {
+ "is_whole_disk_image": False,
+ }
+
+
def get_test_pxe_instance_info():
return {
"image_source": "glance://image_uuid",
diff --git a/ironic/tests/drivers/pxe_config.template b/ironic/tests/drivers/pxe_config.template
index d4dad5007..c6a28313a 100644
--- a/ironic/tests/drivers/pxe_config.template
+++ b/ironic/tests/drivers/pxe_config.template
@@ -6,6 +6,11 @@ append initrd=/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_ramdisk seli
ipappend 3
-label boot
+label boot_partition
kernel /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/kernel
append initrd=/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/ramdisk root={{ ROOT }} ro text test_param
+
+
+label boot_whole_disk
+COM32 chain.c32
+append mbr:{{ DISK_IDENTIFIER }}
diff --git a/ironic/tests/drivers/test_deploy_utils.py b/ironic/tests/drivers/test_deploy_utils.py
index 6308a769b..6789b96e6 100644
--- a/ironic/tests/drivers/test_deploy_utils.py
+++ b/ironic/tests/drivers/test_deploy_utils.py
@@ -27,6 +27,7 @@ from oslo_concurrency import processutils
from oslo_config import cfg
from oslo_utils import uuidutils
import requests
+import testtools
from ironic.common import boot_devices
from ironic.common import disk_partitioner
@@ -51,22 +52,47 @@ kernel deploy_kernel
append initrd=deploy_ramdisk
ipappend 3
-label boot
+label boot_partition
kernel kernel
append initrd=ramdisk root={{ ROOT }}
+
+label boot_whole_disk
+COM32 chain.c32
+append mbr:{{ DISK_IDENTIFIER }}
"""
-_PXECONF_BOOT = """
-default boot
+_PXECONF_BOOT_PARTITION = """
+default boot_partition
label deploy
kernel deploy_kernel
append initrd=deploy_ramdisk
ipappend 3
-label boot
+label boot_partition
kernel kernel
append initrd=ramdisk root=UUID=12345678-1234-1234-1234-1234567890abcdef
+
+label boot_whole_disk
+COM32 chain.c32
+append mbr:{{ DISK_IDENTIFIER }}
+"""
+
+_PXECONF_BOOT_WHOLE_DISK = """
+default boot_whole_disk
+
+label deploy
+kernel deploy_kernel
+append initrd=deploy_ramdisk
+ipappend 3
+
+label boot_partition
+kernel kernel
+append initrd=ramdisk root={{ ROOT }}
+
+label boot_whole_disk
+COM32 chain.c32
+append mbr:0x12345678
"""
_IPXECONF_DEPLOY = """
@@ -81,28 +107,61 @@ kernel deploy_kernel
initrd deploy_ramdisk
boot
-:boot
+:boot_partition
kernel kernel
append initrd=ramdisk root={{ ROOT }}
boot
+
+:boot_whole_disk
+kernel chain.c32
+append mbr:{{ DISK_IDENTIFIER }}
+boot
"""
-_IPXECONF_BOOT = """
+_IPXECONF_BOOT_PARTITION = """
#!ipxe
dhcp
-goto boot
+goto boot_partition
:deploy
kernel deploy_kernel
initrd deploy_ramdisk
boot
-:boot
+:boot_partition
kernel kernel
append initrd=ramdisk root=UUID=12345678-1234-1234-1234-1234567890abcdef
boot
+
+:boot_whole_disk
+kernel chain.c32
+append mbr:{{ DISK_IDENTIFIER }}
+boot
+"""
+
+_IPXECONF_BOOT_WHOLE_DISK = """
+#!ipxe
+
+dhcp
+
+goto boot_whole_disk
+
+:deploy
+kernel deploy_kernel
+initrd deploy_ramdisk
+boot
+
+:boot_partition
+kernel kernel
+append initrd=ramdisk root={{ ROOT }}
+boot
+
+:boot_whole_disk
+kernel chain.c32
+append mbr:0x12345678
+boot
"""
_UEFI_PXECONF_DEPLOY = """
@@ -114,13 +173,17 @@ image=deploy_kernel
append="ro text"
image=kernel
- label=boot
+ label=boot_partition
initrd=ramdisk
append="root={{ ROOT }}"
+
+image=chain.c32
+ label=boot_whole_disk
+ append mbr:{{ DISK_IDENTIFIER }}
"""
-_UEFI_PXECONF_BOOT = """
-default=boot
+_UEFI_PXECONF_BOOT_PARTITION = """
+default=boot_partition
image=deploy_kernel
label=deploy
@@ -128,9 +191,31 @@ image=deploy_kernel
append="ro text"
image=kernel
- label=boot
+ label=boot_partition
initrd=ramdisk
append="root=UUID=12345678-1234-1234-1234-1234567890abcdef"
+
+image=chain.c32
+ label=boot_whole_disk
+ append mbr:{{ DISK_IDENTIFIER }}
+"""
+
+_UEFI_PXECONF_BOOT_WHOLE_DISK = """
+default=boot_whole_disk
+
+image=deploy_kernel
+ label=deploy
+ initrd=deploy_ramdisk
+ append="ro text"
+
+image=kernel
+ label=boot_partition
+ initrd=ramdisk
+ append="root={{ ROOT }}"
+
+image=chain.c32
+ label=boot_whole_disk
+ append mbr:0x12345678
"""
@@ -148,7 +233,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
parent_mock.attach_mock(mocker, name)
return parent_mock
- def _test_deploy(self, boot_option=None):
+ def _test_deploy_partition_image(self, boot_option=None):
"""Check loosely all functions are called with right args."""
address = '127.0.0.1'
port = 3306
@@ -179,10 +264,10 @@ class PhysicalWorkTestCase(tests_base.TestCase):
parent_mock.make_partitions.return_value = {'root': root_part,
'swap': swap_part}
calls_expected = [mock.call.get_dev(address, port, iqn, lun),
- mock.call.get_image_mb(image_path),
mock.call.discovery(address, port),
mock.call.login_iscsi(address, port, iqn),
mock.call.is_block_device(dev),
+ mock.call.get_image_mb(image_path),
mock.call.destroy_disk_metadata(dev, node_uuid)]
if boot_option:
@@ -207,24 +292,26 @@ class PhysicalWorkTestCase(tests_base.TestCase):
if boot_option:
kwargs = {'boot_option': boot_option}
- returned_root_uuid = utils.deploy(address, port, iqn, lun,
- image_path, root_mb, swap_mb,
- ephemeral_mb, ephemeral_format,
- node_uuid, **kwargs)
+ returned_root_uuid = utils.deploy_partition_image(address, port, iqn,
+ lun, image_path,
+ root_mb, swap_mb,
+ ephemeral_mb,
+ ephemeral_format,
+ node_uuid, **kwargs)
self.assertEqual(calls_expected, parent_mock.mock_calls)
self.assertEqual(root_uuid, returned_root_uuid)
- def test_deploy_without_boot_option(self):
- self._test_deploy()
+ def test_deploy_partition_image_without_boot_option(self):
+ self._test_deploy_partition_image()
- def test_deploy_netboot(self):
- self._test_deploy(boot_option="netboot")
+ def test_deploy_partition_image_netboot(self):
+ self._test_deploy_partition_image(boot_option="netboot")
- def test_deploy_localboot(self):
- self._test_deploy(boot_option="local")
+ def test_deploy_partition_image_localboot(self):
+ self._test_deploy_partition_image(boot_option="local")
- def test_deploy_without_swap(self):
+ def test_deploy_partition_image_without_swap(self):
"""Check loosely all functions are called with right args."""
address = '127.0.0.1'
port = 3306
@@ -253,10 +340,10 @@ class PhysicalWorkTestCase(tests_base.TestCase):
parent_mock.block_uuid.return_value = root_uuid
parent_mock.make_partitions.return_value = {'root': root_part}
calls_expected = [mock.call.get_dev(address, port, iqn, lun),
- mock.call.get_image_mb(image_path),
mock.call.discovery(address, port),
mock.call.login_iscsi(address, port, iqn),
mock.call.is_block_device(dev),
+ mock.call.get_image_mb(image_path),
mock.call.destroy_disk_metadata(dev, node_uuid),
mock.call.make_partitions(dev, root_mb, swap_mb,
ephemeral_mb,
@@ -269,15 +356,17 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock.call.logout_iscsi(address, port, iqn),
mock.call.delete_iscsi(address, port, iqn)]
- returned_root_uuid = utils.deploy(address, port, iqn, lun,
- image_path, root_mb, swap_mb,
- ephemeral_mb, ephemeral_format,
- node_uuid)
+ returned_root_uuid = utils.deploy_partition_image(address, port, iqn,
+ lun, image_path,
+ root_mb, swap_mb,
+ ephemeral_mb,
+ ephemeral_format,
+ node_uuid)
self.assertEqual(calls_expected, parent_mock.mock_calls)
self.assertEqual(root_uuid, returned_root_uuid)
- def test_deploy_with_ephemeral(self):
+ def test_deploy_partition_image_with_ephemeral(self):
"""Check loosely all functions are called with right args."""
address = '127.0.0.1'
port = 3306
@@ -311,10 +400,10 @@ class PhysicalWorkTestCase(tests_base.TestCase):
'ephemeral': ephemeral_part,
'root': root_part}
calls_expected = [mock.call.get_dev(address, port, iqn, lun),
- mock.call.get_image_mb(image_path),
mock.call.discovery(address, port),
mock.call.login_iscsi(address, port, iqn),
mock.call.is_block_device(dev),
+ mock.call.get_image_mb(image_path),
mock.call.destroy_disk_metadata(dev, node_uuid),
mock.call.make_partitions(dev, root_mb, swap_mb,
ephemeral_mb,
@@ -332,15 +421,17 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock.call.logout_iscsi(address, port, iqn),
mock.call.delete_iscsi(address, port, iqn)]
- returned_root_uuid = utils.deploy(address, port, iqn, lun,
- image_path, root_mb, swap_mb,
- ephemeral_mb, ephemeral_format,
- node_uuid)
+ returned_root_uuid = utils.deploy_partition_image(address, port, iqn,
+ lun, image_path,
+ root_mb, swap_mb,
+ ephemeral_mb,
+ ephemeral_format,
+ node_uuid)
self.assertEqual(calls_expected, parent_mock.mock_calls)
self.assertEqual(root_uuid, returned_root_uuid)
- def test_deploy_preserve_ephemeral(self):
+ def test_deploy_partition_image_preserve_ephemeral(self):
"""Check if all functions are called with right args."""
address = '127.0.0.1'
port = 3306
@@ -375,10 +466,10 @@ class PhysicalWorkTestCase(tests_base.TestCase):
'root': root_part}
parent_mock.block_uuid.return_value = root_uuid
calls_expected = [mock.call.get_dev(address, port, iqn, lun),
- mock.call.get_image_mb(image_path),
mock.call.discovery(address, port),
mock.call.login_iscsi(address, port, iqn),
mock.call.is_block_device(dev),
+ mock.call.get_image_mb(image_path),
mock.call.make_partitions(dev, root_mb, swap_mb,
ephemeral_mb,
configdrive_mb,
@@ -393,18 +484,21 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock.call.logout_iscsi(address, port, iqn),
mock.call.delete_iscsi(address, port, iqn)]
- returned_root_uuid = utils.deploy(address, port, iqn, lun,
- image_path, root_mb, swap_mb,
- ephemeral_mb, ephemeral_format,
- node_uuid, preserve_ephemeral=True,
- boot_option="netboot")
+ returned_root_uuid = utils.deploy_partition_image(address, port, iqn,
+ lun, image_path,
+ root_mb, swap_mb,
+ ephemeral_mb,
+ ephemeral_format,
+ node_uuid,
+ preserve_ephemeral=True,
+ boot_option="netboot")
self.assertEqual(calls_expected, parent_mock.mock_calls)
self.assertFalse(parent_mock.mkfs_ephemeral.called)
self.assertFalse(parent_mock.get_dev_block_size.called)
self.assertEqual(root_uuid, returned_root_uuid)
@mock.patch.object(common_utils, 'unlink_without_raise')
- def test_deploy_with_configdrive(self, mock_unlink):
+ def test_deploy_partition_image_with_configdrive(self, mock_unlink):
"""Check loosely all functions are called with right args."""
address = '127.0.0.1'
port = 3306
@@ -439,10 +533,10 @@ class PhysicalWorkTestCase(tests_base.TestCase):
configdrive_part}
parent_mock._get_configdrive.return_value = (10, 'configdrive-path')
calls_expected = [mock.call.get_dev(address, port, iqn, lun),
- mock.call.get_image_mb(image_path),
mock.call.discovery(address, port),
mock.call.login_iscsi(address, port, iqn),
mock.call.is_block_device(dev),
+ mock.call.get_image_mb(image_path),
mock.call.destroy_disk_metadata(dev, node_uuid),
mock.call._get_configdrive(configdrive_url,
node_uuid),
@@ -459,16 +553,49 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock.call.logout_iscsi(address, port, iqn),
mock.call.delete_iscsi(address, port, iqn)]
- returned_root_uuid = utils.deploy(address, port, iqn, lun,
- image_path, root_mb, swap_mb,
- ephemeral_mb, ephemeral_format,
- node_uuid,
- configdrive=configdrive_url)
+ returned_root_uuid = utils.deploy_partition_image(address, port, iqn,
+ lun, image_path,
+ root_mb, swap_mb,
+ ephemeral_mb,
+ ephemeral_format,
+ node_uuid,
+ configdrive=configdrive_url)
self.assertEqual(calls_expected, parent_mock.mock_calls)
self.assertEqual(root_uuid, returned_root_uuid)
mock_unlink.assert_called_once_with('configdrive-path')
+ @mock.patch.object(utils, 'get_disk_identifier')
+ def test_deploy_whole_disk_image(self, mock_gdi):
+ """Check loosely all functions are called with right args."""
+ address = '127.0.0.1'
+ port = 3306
+ iqn = 'iqn.xyz'
+ lun = 1
+ image_path = '/tmp/xyz/image'
+ node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
+
+ dev = '/dev/fake'
+ name_list = ['get_dev', 'discovery', 'login_iscsi', 'logout_iscsi',
+ 'delete_iscsi', 'is_block_device', 'populate_image',
+ 'notify']
+ parent_mock = self._mock_calls(name_list)
+ parent_mock.get_dev.return_value = dev
+ parent_mock.is_block_device.return_value = True
+ mock_gdi.return_value = '0x12345678'
+ calls_expected = [mock.call.get_dev(address, port, iqn, lun),
+ mock.call.discovery(address, port),
+ mock.call.login_iscsi(address, port, iqn),
+ mock.call.is_block_device(dev),
+ mock.call.populate_image(image_path, dev),
+ mock.call.logout_iscsi(address, port, iqn),
+ mock.call.delete_iscsi(address, port, iqn)]
+
+ utils.deploy_disk_image(address, port, iqn, lun,
+ image_path, node_uuid)
+
+ self.assertEqual(calls_expected, parent_mock.mock_calls)
+
@mock.patch.object(common_utils, 'execute')
def test_verify_iscsi_connection_raises(self, mock_exec):
iqn = 'iqn.xyz'
@@ -552,6 +679,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock_check_dev.assert_called_once_with(address, port, iqn)
+ @mock.patch.object(utils, 'is_block_device', lambda d: True)
def test_always_logout_and_delete_iscsi(self):
"""Check if logout_iscsi() and delete_iscsi() are called.
@@ -590,9 +718,9 @@ class PhysicalWorkTestCase(tests_base.TestCase):
parent_mock.get_image_mb.return_value = 1
parent_mock.work_on_disk.side_effect = TestException
calls_expected = [mock.call.get_dev(address, port, iqn, lun),
- mock.call.get_image_mb(image_path),
mock.call.discovery(address, port),
mock.call.login_iscsi(address, port, iqn),
+ mock.call.get_image_mb(image_path),
mock.call.work_on_disk(dev, root_mb, swap_mb,
ephemeral_mb,
ephemeral_format, image_path,
@@ -602,7 +730,7 @@ class PhysicalWorkTestCase(tests_base.TestCase):
mock.call.logout_iscsi(address, port, iqn),
mock.call.delete_iscsi(address, port, iqn)]
- self.assertRaises(TestException, utils.deploy,
+ self.assertRaises(TestException, utils.deploy_partition_image,
address, port, iqn, lun, image_path,
root_mb, swap_mb, ephemeral_mb, ephemeral_format,
node_uuid)
@@ -623,36 +751,73 @@ class SwitchPxeConfigTestCase(tests_base.TestCase):
self.addCleanup(os.unlink, fname)
return fname
- def test_switch_pxe_config(self):
+ def test_switch_pxe_config_partition_image(self):
boot_mode = 'bios'
fname = self._create_config()
utils.switch_pxe_config(fname,
- '12345678-1234-1234-1234-1234567890abcdef',
- boot_mode)
+ '12345678-1234-1234-1234-1234567890abcdef',
+ boot_mode,
+ False)
with open(fname, 'r') as f:
pxeconf = f.read()
- self.assertEqual(_PXECONF_BOOT, pxeconf)
+ self.assertEqual(_PXECONF_BOOT_PARTITION, pxeconf)
- def test_switch_ipxe_config(self):
+ def test_switch_pxe_config_whole_disk_image(self):
+ boot_mode = 'bios'
+ fname = self._create_config()
+ utils.switch_pxe_config(fname,
+ '0x12345678',
+ boot_mode,
+ True)
+ with open(fname, 'r') as f:
+ pxeconf = f.read()
+ self.assertEqual(_PXECONF_BOOT_WHOLE_DISK, pxeconf)
+
+ def test_switch_ipxe_config_partition_image(self):
+ boot_mode = 'bios'
+ cfg.CONF.set_override('ipxe_enabled', True, 'pxe')
+ fname = self._create_config(ipxe=True)
+ utils.switch_pxe_config(fname,
+ '12345678-1234-1234-1234-1234567890abcdef',
+ boot_mode,
+ False)
+ with open(fname, 'r') as f:
+ pxeconf = f.read()
+ self.assertEqual(_IPXECONF_BOOT_PARTITION, pxeconf)
+
+ def test_switch_ipxe_config_whole_disk_image(self):
boot_mode = 'bios'
cfg.CONF.set_override('ipxe_enabled', True, 'pxe')
fname = self._create_config(ipxe=True)
utils.switch_pxe_config(fname,
- '12345678-1234-1234-1234-1234567890abcdef',
- boot_mode)
+ '0x12345678',
+ boot_mode,
+ True)
with open(fname, 'r') as f:
pxeconf = f.read()
- self.assertEqual(_IPXECONF_BOOT, pxeconf)
+ self.assertEqual(_IPXECONF_BOOT_WHOLE_DISK, pxeconf)
- def test_switch_uefi_pxe_config(self):
+ def test_switch_uefi_pxe_config_partition_image(self):
boot_mode = 'uefi'
fname = self._create_config(boot_mode=boot_mode)
utils.switch_pxe_config(fname,
- '12345678-1234-1234-1234-1234567890abcdef',
- boot_mode)
+ '12345678-1234-1234-1234-1234567890abcdef',
+ boot_mode,
+ False)
with open(fname, 'r') as f:
pxeconf = f.read()
- self.assertEqual(_UEFI_PXECONF_BOOT, pxeconf)
+ self.assertEqual(_UEFI_PXECONF_BOOT_PARTITION, pxeconf)
+
+ def test_switch_uefi_config_whole_disk_image(self):
+ boot_mode = 'uefi'
+ fname = self._create_config(boot_mode=boot_mode)
+ utils.switch_pxe_config(fname,
+ '0x12345678',
+ boot_mode,
+ True)
+ with open(fname, 'r') as f:
+ pxeconf = f.read()
+ self.assertEqual(_UEFI_PXECONF_BOOT_WHOLE_DISK, pxeconf)
@mock.patch('time.sleep', lambda sec: None)
@@ -727,34 +892,21 @@ class WorkOnDiskTestCase(tests_base.TestCase):
self.mock_mp.return_value = {'swap': self.swap_part,
'root': self.root_part}
- def test_no_parent_device(self):
- self.mock_ibd.return_value = False
- self.assertRaises(exception.InstanceDeployFailure,
- utils.work_on_disk, self.dev, self.root_mb,
- self.swap_mb, self.ephemeral_mb,
- self.ephemeral_format, self.image_path, 'fake-uuid')
- self.mock_ibd.assert_called_once_with(self.dev)
- self.assertFalse(self.mock_mp.called,
- "make_partitions mock was unexpectedly called.")
-
def test_no_root_partition(self):
- self.mock_ibd.side_effect = [True, False]
- calls = [mock.call(self.dev),
- mock.call(self.root_part)]
+ self.mock_ibd.return_value = False
self.assertRaises(exception.InstanceDeployFailure,
utils.work_on_disk, self.dev, self.root_mb,
self.swap_mb, self.ephemeral_mb,
self.ephemeral_format, self.image_path, 'fake-uuid')
- self.assertEqual(self.mock_ibd.call_args_list, calls)
+ self.mock_ibd.assert_called_once_with(self.root_part)
self.mock_mp.assert_called_once_with(self.dev, self.root_mb,
self.swap_mb, self.ephemeral_mb,
self.configdrive_mb, commit=True,
boot_option="netboot")
def test_no_swap_partition(self):
- self.mock_ibd.side_effect = [True, True, False]
- calls = [mock.call(self.dev),
- mock.call(self.root_part),
+ self.mock_ibd.side_effect = [True, False]
+ calls = [mock.call(self.root_part),
mock.call(self.swap_part)]
self.assertRaises(exception.InstanceDeployFailure,
utils.work_on_disk, self.dev, self.root_mb,
@@ -776,9 +928,8 @@ class WorkOnDiskTestCase(tests_base.TestCase):
self.mock_mp.return_value = {'ephemeral': ephemeral_part,
'swap': swap_part,
'root': root_part}
- self.mock_ibd.side_effect = [True, True, True, False]
- calls = [mock.call(self.dev),
- mock.call(root_part),
+ self.mock_ibd.side_effect = [True, True, False]
+ calls = [mock.call(root_part),
mock.call(swap_part),
mock.call(ephemeral_part)]
self.assertRaises(exception.InstanceDeployFailure,
@@ -804,9 +955,8 @@ class WorkOnDiskTestCase(tests_base.TestCase):
self.mock_mp.return_value = {'swap': swap_part,
'configdrive': configdrive_part,
'root': root_part}
- self.mock_ibd.side_effect = [True, True, True, False]
- calls = [mock.call(self.dev),
- mock.call(root_part),
+ self.mock_ibd.side_effect = [True, True, False]
+ calls = [mock.call(root_part),
mock.call(swap_part),
mock.call(configdrive_part)]
self.assertRaises(exception.InstanceDeployFailure,
@@ -1217,3 +1367,41 @@ class TrySetBootDeviceTestCase(db_base.DbTestCase):
task, boot_devices.DISK, persistent=True)
node_set_boot_device_mock.assert_called_once_with(
task, boot_devices.DISK, persistent=True)
+
+
+@mock.patch.object(utils, 'is_block_device')
+@mock.patch.object(utils, 'login_iscsi', lambda *_: None)
+@mock.patch.object(utils, 'discovery', lambda *_: None)
+@mock.patch.object(utils, 'logout_iscsi', lambda *_: None)
+@mock.patch.object(utils, 'delete_iscsi', lambda *_: None)
+@mock.patch.object(utils, 'get_dev', lambda *_: '/dev/fake')
+class ISCSISetupAndHandleErrorsTestCase(tests_base.TestCase):
+
+ def test_no_parent_device(self, mock_ibd):
+ address = '127.0.0.1'
+ port = 3306
+ iqn = 'iqn.xyz'
+ lun = 1
+ image_path = '/tmp/xyz/image'
+ mock_ibd.return_value = False
+ expected_dev = '/dev/fake'
+ with testtools.ExpectedException(exception.InstanceDeployFailure):
+ with utils._iscsi_setup_and_handle_errors(
+ address, port, iqn, lun, image_path) as dev:
+ self.assertEqual(expected_dev, dev)
+
+ mock_ibd.assert_called_once_with(expected_dev)
+
+ def test_parent_device_yield(self, mock_ibd):
+ address = '127.0.0.1'
+ port = 3306
+ iqn = 'iqn.xyz'
+ lun = 1
+ image_path = '/tmp/xyz/image'
+ expected_dev = '/dev/fake'
+ mock_ibd.return_value = True
+ with utils._iscsi_setup_and_handle_errors(address, port,
+ iqn, lun, image_path) as dev:
+ self.assertEqual(expected_dev, dev)
+
+ mock_ibd.assert_called_once_with(expected_dev)
diff --git a/ironic/tests/drivers/test_iscsi_deploy.py b/ironic/tests/drivers/test_iscsi_deploy.py
index 9e6f5c789..379904fdf 100644
--- a/ironic/tests/drivers/test_iscsi_deploy.py
+++ b/ironic/tests/drivers/test_iscsi_deploy.py
@@ -42,15 +42,18 @@ CONF = cfg.CONF
INST_INFO_DICT = db_utils.get_test_pxe_instance_info()
DRV_INFO_DICT = db_utils.get_test_pxe_driver_info()
+DRV_INTERNAL_INFO_DICT = db_utils.get_test_pxe_driver_internal_info()
class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
def test_parse_instance_info_good(self):
# make sure we get back the expected things
- node = obj_utils.create_test_node(self.context,
- driver='fake_pxe',
- instance_info=INST_INFO_DICT)
+ node = obj_utils.create_test_node(
+ self.context, driver='fake_pxe',
+ instance_info=INST_INFO_DICT,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT
+ )
info = iscsi_deploy.parse_instance_info(node)
self.assertIsNotNone(info.get('image_source'))
self.assertIsNotNone(info.get('root_gb'))
@@ -61,7 +64,10 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
# make sure error is raised when info is missing
info = dict(INST_INFO_DICT)
del info['image_source']
- node = obj_utils.create_test_node(self.context, instance_info=info)
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
self.assertRaises(exception.MissingParameterValue,
iscsi_deploy.parse_instance_info,
node)
@@ -70,7 +76,11 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
# make sure error is raised when info is missing
info = dict(INST_INFO_DICT)
del info['root_gb']
- node = obj_utils.create_test_node(self.context, instance_info=info)
+
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
self.assertRaises(exception.MissingParameterValue,
iscsi_deploy.parse_instance_info,
node)
@@ -78,7 +88,10 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
def test_parse_instance_info_invalid_root_gb(self):
info = dict(INST_INFO_DICT)
info['root_gb'] = 'foobar'
- node = obj_utils.create_test_node(self.context, instance_info=info)
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
self.assertRaises(exception.InvalidParameterValue,
iscsi_deploy.parse_instance_info,
node)
@@ -89,7 +102,10 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
info = dict(INST_INFO_DICT)
info['ephemeral_gb'] = ephemeral_gb
info['ephemeral_format'] = ephemeral_fmt
- node = obj_utils.create_test_node(self.context, instance_info=info)
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
data = iscsi_deploy.parse_instance_info(node)
self.assertEqual(ephemeral_gb, data.get('ephemeral_gb'))
self.assertEqual(ephemeral_fmt, data.get('ephemeral_format'))
@@ -98,7 +114,11 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
info = dict(INST_INFO_DICT)
info['ephemeral_gb'] = 'foobar'
info['ephemeral_format'] = 'exttest'
- node = obj_utils.create_test_node(self.context, instance_info=info)
+
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
self.assertRaises(exception.InvalidParameterValue,
iscsi_deploy.parse_instance_info,
node)
@@ -110,7 +130,10 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
info['ephemeral_gb'] = ephemeral_gb
info['ephemeral_format'] = None
self.config(default_ephemeral_format=ephemeral_fmt, group='pxe')
- node = obj_utils.create_test_node(self.context, instance_info=info)
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
instance_info = iscsi_deploy.parse_instance_info(node)
self.assertEqual(ephemeral_fmt, instance_info['ephemeral_format'])
@@ -119,9 +142,12 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
for opt in ['true', 'TRUE', 'True', 't',
'on', 'yes', 'y', '1']:
info['preserve_ephemeral'] = opt
- node = obj_utils.create_test_node(self.context,
- uuid=uuidutils.generate_uuid(),
- instance_info=info)
+
+ node = obj_utils.create_test_node(
+ self.context, uuid=uuidutils.generate_uuid(),
+ instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
data = iscsi_deploy.parse_instance_info(node)
self.assertTrue(data.get('preserve_ephemeral'))
@@ -130,16 +156,21 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
for opt in ['false', 'FALSE', 'False', 'f',
'off', 'no', 'n', '0']:
info['preserve_ephemeral'] = opt
- node = obj_utils.create_test_node(self.context,
- uuid=uuidutils.generate_uuid(),
- instance_info=info)
+ node = obj_utils.create_test_node(
+ self.context, uuid=uuidutils.generate_uuid(),
+ instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
data = iscsi_deploy.parse_instance_info(node)
self.assertFalse(data.get('preserve_ephemeral'))
def test_parse_instance_info_invalid_preserve_ephemeral(self):
info = dict(INST_INFO_DICT)
info['preserve_ephemeral'] = 'foobar'
- node = obj_utils.create_test_node(self.context, instance_info=info)
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
self.assertRaises(exception.InvalidParameterValue,
iscsi_deploy.parse_instance_info,
node)
@@ -147,7 +178,10 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
def test_parse_instance_info_configdrive(self):
info = dict(INST_INFO_DICT)
info['configdrive'] = 'http://1.2.3.4/cd'
- node = obj_utils.create_test_node(self.context, instance_info=info)
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
instance_info = iscsi_deploy.parse_instance_info(node)
self.assertEqual('http://1.2.3.4/cd', instance_info['configdrive'])
@@ -156,23 +190,31 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
info['image_source'] = 'file:///image.qcow2'
info['kernel'] = 'file:///image.vmlinuz'
info['ramdisk'] = 'file:///image.initrd'
- node = obj_utils.create_test_node(self.context, instance_info=info)
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
iscsi_deploy.parse_instance_info(node)
def test_parse_instance_info_nonglance_image_no_kernel(self):
info = INST_INFO_DICT.copy()
info['image_source'] = 'file:///image.qcow2'
info['ramdisk'] = 'file:///image.initrd'
- node = obj_utils.create_test_node(self.context, instance_info=info)
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
self.assertRaises(exception.MissingParameterValue,
iscsi_deploy.parse_instance_info, node)
@mock.patch.object(image_service, 'get_image_service')
def test_validate_image_properties_glance_image(self, image_service_mock):
- node = obj_utils.create_test_node(self.context,
- driver='fake_pxe',
- instance_info=INST_INFO_DICT,
- driver_info=DRV_INFO_DICT)
+ node = obj_utils.create_test_node(
+ self.context, driver='fake_pxe',
+ instance_info=INST_INFO_DICT,
+ driver_info=DRV_INFO_DICT,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
d_info = pxe._parse_deploy_info(node)
image_service_mock.return_value.show.return_value = {
'properties': {'kernel_id': '1111', 'ramdisk_id': '2222'},
@@ -187,10 +229,12 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
@mock.patch.object(image_service, 'get_image_service')
def test_validate_image_properties_glance_image_missing_prop(self,
image_service_mock):
- node = obj_utils.create_test_node(self.context,
- driver='fake_pxe',
- instance_info=INST_INFO_DICT,
- driver_info=DRV_INFO_DICT)
+ node = obj_utils.create_test_node(
+ self.context, driver='fake_pxe',
+ instance_info=INST_INFO_DICT,
+ driver_info=DRV_INFO_DICT,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
d_info = pxe._parse_deploy_info(node)
image_service_mock.return_value.show.return_value = {
'properties': {'kernel_id': '1111'},
@@ -239,10 +283,12 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
'root_gb': 100,
}
image_service_show_mock.return_value = {'size': 1, 'properties': {}}
- node = obj_utils.create_test_node(self.context,
- driver='fake_pxe',
- instance_info=instance_info,
- driver_info=DRV_INFO_DICT)
+ node = obj_utils.create_test_node(
+ self.context, driver='fake_pxe',
+ instance_info=instance_info,
+ driver_info=DRV_INFO_DICT,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
d_info = pxe._parse_deploy_info(node)
iscsi_deploy.validate_image_properties(self.context, d_info,
['kernel', 'ramdisk'])
@@ -260,15 +306,38 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
}
img_service_show_mock.side_effect = exception.ImageRefValidationFailed(
image_href='http://ubuntu', reason='HTTPError')
- node = obj_utils.create_test_node(self.context,
- driver='fake_pxe',
- instance_info=instance_info,
- driver_info=DRV_INFO_DICT)
+ node = obj_utils.create_test_node(
+ self.context, driver='fake_pxe',
+ instance_info=instance_info,
+ driver_info=DRV_INFO_DICT,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
d_info = pxe._parse_deploy_info(node)
self.assertRaises(exception.InvalidParameterValue,
iscsi_deploy.validate_image_properties, self.context,
d_info, ['kernel', 'ramdisk'])
+ def test_parse_instance_info_whole_disk_image(self):
+ driver_internal_info = dict(DRV_INTERNAL_INFO_DICT)
+ driver_internal_info['is_whole_disk_image'] = True
+ node = obj_utils.create_test_node(
+ self.context, instance_info=INST_INFO_DICT,
+ driver_internal_info=driver_internal_info,
+ )
+ instance_info = iscsi_deploy.parse_instance_info(node)
+ self.assertIsNotNone(instance_info.get('image_source'))
+ self.assertIsNotNone(instance_info.get('root_gb'))
+ self.assertEqual(0, instance_info.get('swap_mb'))
+ self.assertEqual(0, instance_info.get('ephemeral_gb'))
+ self.assertIsNone(instance_info.get('configdrive'))
+
+ def test_parse_instance_info_whole_disk_image_missing_root(self):
+ info = dict(INST_INFO_DICT)
+ del info['root_gb']
+ node = obj_utils.create_test_node(self.context, instance_info=info)
+ self.assertRaises(exception.InvalidParameterValue,
+ iscsi_deploy.parse_instance_info, node)
+
class IscsiDeployPrivateMethodsTestCase(db_base.DbTestCase):
@@ -278,6 +347,7 @@ class IscsiDeployPrivateMethodsTestCase(db_base.DbTestCase):
'driver': 'fake_pxe',
'instance_info': INST_INFO_DICT,
'driver_info': DRV_INFO_DICT,
+ 'driver_internal_info': DRV_INTERNAL_INFO_DICT,
}
mgr_utils.mock_the_extension_manager(driver="fake_pxe")
self.node = obj_utils.create_test_node(self.context, **n)
@@ -304,6 +374,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
'driver': 'fake_pxe',
'instance_info': instance_info,
'driver_info': DRV_INFO_DICT,
+ 'driver_internal_info': DRV_INTERNAL_INFO_DICT,
}
mgr_utils.mock_the_extension_manager(driver="fake_pxe")
self.node = obj_utils.create_test_node(self.context, **n)
@@ -440,7 +511,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
@mock.patch.object(iscsi_deploy, 'InstanceImageCache')
@mock.patch.object(manager_utils, 'node_power_action')
- @mock.patch.object(deploy_utils, 'deploy')
+ @mock.patch.object(deploy_utils, 'deploy_partition_image')
def test_continue_deploy_fail(self, deploy_mock, power_mock,
mock_image_cache):
kwargs = {'address': '123456', 'iqn': 'aaa-bbb', 'key': 'fake-56789'}
@@ -464,7 +535,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
@mock.patch.object(iscsi_deploy, 'InstanceImageCache')
@mock.patch.object(manager_utils, 'node_power_action')
- @mock.patch.object(deploy_utils, 'deploy')
+ @mock.patch.object(deploy_utils, 'deploy_partition_image')
def test_continue_deploy_ramdisk_fails(self, deploy_mock, power_mock,
mock_image_cache):
kwargs = {'address': '123456', 'iqn': 'aaa-bbb', 'key': 'fake-56789',
@@ -488,7 +559,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
@mock.patch.object(iscsi_deploy, 'get_deploy_info')
@mock.patch.object(iscsi_deploy, 'InstanceImageCache')
@mock.patch.object(manager_utils, 'node_power_action')
- @mock.patch.object(deploy_utils, 'deploy')
+ @mock.patch.object(deploy_utils, 'deploy_partition_image')
def test_continue_deploy(self, deploy_mock, power_mock, mock_image_cache,
mock_deploy_info, mock_log):
kwargs = {'address': '123456', 'iqn': 'aaa-bbb', 'key': 'fake-56789'}
@@ -531,6 +602,47 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
mock_image_cache.assert_called_once_with()
mock_image_cache.return_value.clean_up.assert_called_once_with()
+ @mock.patch.object(iscsi_deploy, 'LOG')
+ @mock.patch.object(iscsi_deploy, 'get_deploy_info')
+ @mock.patch.object(iscsi_deploy, 'InstanceImageCache')
+ @mock.patch.object(manager_utils, 'node_power_action')
+ @mock.patch.object(deploy_utils, 'deploy_disk_image')
+ def test_continue_deploy_whole_disk_image(
+ self, deploy_mock, power_mock, mock_image_cache, mock_deploy_info,
+ mock_log):
+ kwargs = {'address': '123456', 'iqn': 'aaa-bbb', 'key': 'fake-56789'}
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+
+ mock_deploy_info.return_value = {
+ 'address': '123456',
+ 'image_path': (u'/var/lib/ironic/images/1be26c0b-03f2-4d2e-ae87-'
+ u'c02d7f33c123/disk'),
+ 'iqn': 'aaa-bbb',
+ 'lun': '1',
+ 'node_uuid': u'1be26c0b-03f2-4d2e-ae87-c02d7f33c123',
+ 'port': '3260',
+ 'root_mb': 102400,
+ }
+ log_params = mock_deploy_info.return_value.copy()
+ expected_dict = {
+ 'node': self.node.uuid,
+ 'params': log_params,
+ }
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ task.node.driver_internal_info['is_whole_disk_image'] = True
+ mock_log.isEnabledFor.return_value = True
+ iscsi_deploy.continue_deploy(task, **kwargs)
+ mock_log.debug.assert_called_once_with(
+ mock.ANY, expected_dict)
+ self.assertEqual(states.DEPLOYWAIT, task.node.provision_state)
+ self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+ self.assertIsNone(task.node.last_error)
+ mock_image_cache.assert_called_once_with()
+ mock_image_cache.return_value.clean_up.assert_called_once_with()
+
def test_get_deploy_info_boot_option_default(self):
instance_info = self.node.instance_info
instance_info['deploy_key'] = 'key'
@@ -588,8 +700,9 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
task, error=None, iqn='iqn-qweqwe', key='abcdef',
address='1.2.3.4')
self.assertEqual('some-root-uuid', ret_val)
- self.assertEqual('some-root-uuid',
- task.node.driver_internal_info['root_uuid'])
+ self.assertEqual(
+ 'some-root-uuid',
+ task.node.driver_internal_info['root_uuid_or_disk_id'])
@mock.patch.object(iscsi_deploy, 'build_deploy_ramdisk_options')
def test_do_agent_iscsi_deploy_start_iscsi_failure(self,
diff --git a/ironic/tests/drivers/test_pxe.py b/ironic/tests/drivers/test_pxe.py
index 03dbef53b..ebc559ae6 100644
--- a/ironic/tests/drivers/test_pxe.py
+++ b/ironic/tests/drivers/test_pxe.py
@@ -50,16 +50,21 @@ CONF = cfg.CONF
INST_INFO_DICT = db_utils.get_test_pxe_instance_info()
DRV_INFO_DICT = db_utils.get_test_pxe_driver_info()
+DRV_INTERNAL_INFO_DICT = db_utils.get_test_pxe_driver_internal_info()
class PXEValidateParametersTestCase(db_base.DbTestCase):
def test__parse_deploy_info(self):
# make sure we get back the expected things
- node = obj_utils.create_test_node(self.context,
- driver='fake_pxe',
- instance_info=INST_INFO_DICT,
- driver_info=DRV_INFO_DICT)
+ node = obj_utils.create_test_node(
+ self.context,
+ driver='fake_pxe',
+ instance_info=INST_INFO_DICT,
+ driver_info=DRV_INFO_DICT,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+
info = pxe._parse_deploy_info(node)
self.assertIsNotNone(info.get('deploy_ramdisk'))
self.assertIsNotNone(info.get('deploy_kernel'))
@@ -114,6 +119,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
'driver': 'fake_pxe',
'instance_info': INST_INFO_DICT,
'driver_info': DRV_INFO_DICT,
+ 'driver_internal_info': DRV_INTERNAL_INFO_DICT,
}
mgr_utils.mock_the_extension_manager(driver="fake_pxe")
self.node = obj_utils.create_test_node(self.context, **n)
@@ -159,6 +165,25 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
self.assertEqual('instance_ramdisk_uuid',
self.node.instance_info.get('ramdisk'))
+ @mock.patch.object(base_image_service.BaseImageService, '_show')
+ def test__get_image_info_whole_disk_image(self, show_mock):
+ properties = {'properties': None}
+
+ expected_info = {'deploy_ramdisk':
+ (DRV_INFO_DICT['deploy_ramdisk'],
+ os.path.join(CONF.pxe.tftp_root,
+ self.node.uuid,
+ 'deploy_ramdisk')),
+ 'deploy_kernel':
+ (DRV_INFO_DICT['deploy_kernel'],
+ os.path.join(CONF.pxe.tftp_root,
+ self.node.uuid,
+ 'deploy_kernel'))}
+ show_mock.return_value = properties
+ self.node.driver_internal_info['is_whole_disk_image'] = True
+ image_info = pxe._get_image_info(self.node, self.context)
+ self.assertEqual(expected_info, image_info)
+
@mock.patch.object(iscsi_deploy, 'build_deploy_ramdisk_options')
@mock.patch.object(pxe_utils, '_build_pxe_config')
def _test_build_pxe_config_options(self, build_pxe_mock, deploy_opts_mock,
@@ -231,9 +256,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
'ramdisk': ('ramdisk_id',
os.path.join(root_dir,
self.node.uuid,
- 'ramdisk'))
- }
-
+ 'ramdisk'))}
options = pxe._build_pxe_config_options(self.node,
image_info,
self.context)
@@ -245,6 +268,66 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
def test__build_pxe_config_options_ipxe(self):
self._test_build_pxe_config_options(ipxe_enabled=True)
+ @mock.patch.object(iscsi_deploy, 'build_deploy_ramdisk_options')
+ @mock.patch.object(pxe_utils, '_build_pxe_config')
+ def _test_build_pxe_config_options_whole_disk_image(self, build_pxe_mock,
+ deploy_opts_mock, ipxe_enabled=False):
+ self.config(pxe_append_params='test_param', group='pxe')
+ # NOTE: right '/' should be removed from url string
+ self.config(api_url='http://192.168.122.184:6385/', group='conductor')
+ self.config(disk_devices='sda', group='pxe')
+
+ fake_deploy_opts = {'iscsi_target_iqn': 'fake-iqn',
+ 'deployment_id': 'fake-deploy-id',
+ 'deployment_key': 'fake-deploy-key',
+ 'disk': 'fake-disk',
+ 'ironic_api_url': 'fake-api-url'}
+
+ deploy_opts_mock.return_value = fake_deploy_opts
+
+ tftp_server = CONF.pxe.tftp_server
+
+ if ipxe_enabled:
+ http_url = 'http://192.1.2.3:1234'
+ self.config(ipxe_enabled=True, group='pxe')
+ self.config(http_url=http_url, group='pxe')
+
+ deploy_kernel = os.path.join(http_url, self.node.uuid,
+ 'deploy_kernel')
+ deploy_ramdisk = os.path.join(http_url, self.node.uuid,
+ 'deploy_ramdisk')
+ root_dir = CONF.pxe.http_root
+ else:
+ deploy_kernel = os.path.join(CONF.pxe.tftp_root, self.node.uuid,
+ 'deploy_kernel')
+ deploy_ramdisk = os.path.join(CONF.pxe.tftp_root, self.node.uuid,
+ 'deploy_ramdisk')
+ root_dir = CONF.pxe.tftp_root
+
+ expected_options = {
+ 'deployment_ari_path': deploy_ramdisk,
+ 'pxe_append_params': 'test_param',
+ 'deployment_aki_path': deploy_kernel,
+ 'tftp_server': tftp_server,
+ }
+
+ expected_options.update(fake_deploy_opts)
+
+ image_info = {'deploy_kernel': ('deploy_kernel',
+ os.path.join(root_dir,
+ self.node.uuid,
+ 'deploy_kernel')),
+ 'deploy_ramdisk': ('deploy_ramdisk',
+ os.path.join(root_dir,
+ self.node.uuid,
+ 'deploy_ramdisk')),
+ }
+ self.node.driver_internal_info['is_whole_disk_image'] = True
+ options = pxe._build_pxe_config_options(self.node,
+ image_info,
+ self.context)
+ self.assertEqual(expected_options, options)
+
def test_get_token_file_path(self):
node_uuid = self.node.uuid
self.assertEqual('/tftpboot/token-' + node_uuid,
@@ -311,10 +394,12 @@ class PXEDriverTestCase(db_base.DbTestCase):
mgr_utils.mock_the_extension_manager(driver="fake_pxe")
instance_info = INST_INFO_DICT
instance_info['deploy_key'] = 'fake-56789'
- self.node = obj_utils.create_test_node(self.context,
- driver='fake_pxe',
- instance_info=instance_info,
- driver_info=DRV_INFO_DICT)
+ self.node = obj_utils.create_test_node(
+ self.context,
+ driver='fake_pxe',
+ instance_info=instance_info,
+ driver_info=DRV_INFO_DICT,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT)
self.port = obj_utils.create_test_port(self.context,
node_id=self.node.id)
self.config(group='conductor', api_url='http://127.0.0.1:1234/')
@@ -338,6 +423,13 @@ class PXEDriverTestCase(db_base.DbTestCase):
shared=True) as task:
task.driver.deploy.validate(task)
+ @mock.patch.object(base_image_service.BaseImageService, '_show')
+ def test_validate_good_whole_disk_image(self, mock_glance):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=True) as task:
+ task.node.driver_internal_info['is_whole_disk_image'] = True
+ task.driver.deploy.validate(task)
+
def test_validate_fail(self):
info = dict(INST_INFO_DICT)
del info['image_source']
@@ -570,7 +662,8 @@ class PXEDriverTestCase(db_base.DbTestCase):
mock_get_cap.return_value = None
self.node.provision_state = states.ACTIVE
- self.node.driver_internal_info = {'root_uuid': 'abcd'}
+ self.node.driver_internal_info = {'root_uuid_or_disk_id': 'abcd',
+ 'is_whole_disk_image': False}
self.node.save()
with task_manager.acquire(self.context, self.node.uuid) as task:
@@ -583,7 +676,7 @@ class PXEDriverTestCase(db_base.DbTestCase):
task.node, None)
mock_pxe_get_cfg.assert_called_once_with(task.node.uuid)
- mock_switch.assert_called_once_with('/path', 'abcd', None)
+ mock_switch.assert_called_once_with('/path', 'abcd', None, False)
@mock.patch.object(keystone, 'token_expires_soon')
@mock.patch.object(deploy_utils, 'get_image_mb')
@@ -712,7 +805,7 @@ class PXEDriverTestCase(db_base.DbTestCase):
@mock.patch.object(deploy_utils, 'notify_deploy_complete')
@mock.patch.object(deploy_utils, 'switch_pxe_config')
@mock.patch.object(iscsi_deploy, 'InstanceImageCache')
- @mock.patch.object(deploy_utils, 'deploy')
+ @mock.patch.object(deploy_utils, 'deploy_partition_image')
def _test_continue_deploy(self, is_localboot, mock_deploy,
mock_image_cache, mock_switch_config,
notify_mock, mock_node_boot_dev, mock_clean_pxe):
@@ -732,6 +825,7 @@ class PXEDriverTestCase(db_base.DbTestCase):
root_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
mock_deploy.return_value = root_uuid
boot_mode = None
+ is_whole_disk_image = False
with task_manager.acquire(self.context, self.node.uuid) as task:
task.driver.vendor._continue_deploy(
@@ -741,7 +835,67 @@ class PXEDriverTestCase(db_base.DbTestCase):
self.assertEqual(states.ACTIVE, self.node.provision_state)
self.assertEqual(states.NOSTATE, self.node.target_provision_state)
self.assertEqual(states.POWER_ON, self.node.power_state)
- self.assertIn('root_uuid', self.node.driver_internal_info)
+ self.assertIn('root_uuid_or_disk_id', self.node.driver_internal_info)
+ self.assertIsNone(self.node.last_error)
+ self.assertFalse(os.path.exists(token_path))
+ mock_image_cache.assert_called_once_with()
+ mock_image_cache.return_value.clean_up.assert_called_once_with()
+ pxe_config_path = pxe_utils.get_pxe_config_file_path(self.node.uuid)
+ notify_mock.assert_called_once_with('123456')
+ if is_localboot:
+ mock_node_boot_dev.assert_called_once_with(
+ mock.ANY, boot_devices.DISK, persistent=True)
+ mock_clean_pxe.assert_called_once_with(mock.ANY)
+ self.assertFalse(mock_switch_config.called)
+ else:
+ mock_switch_config.assert_called_once_with(pxe_config_path,
+ root_uuid,
+ boot_mode,
+ is_whole_disk_image)
+ self.assertFalse(mock_node_boot_dev.called)
+ self.assertFalse(mock_clean_pxe.called)
+
+ @mock.patch.object(pxe_utils, 'clean_up_pxe_config')
+ @mock.patch.object(manager_utils, 'node_set_boot_device')
+ @mock.patch.object(deploy_utils, 'notify_deploy_complete')
+ @mock.patch.object(deploy_utils, 'switch_pxe_config')
+ @mock.patch.object(iscsi_deploy, 'InstanceImageCache')
+ @mock.patch.object(deploy_utils, 'deploy_disk_image')
+ def _test_continue_deploy_whole_disk_image(self, is_localboot,
+ mock_deploy,
+ mock_image_cache,
+ mock_switch_config,
+ notify_mock,
+ mock_node_boot_dev,
+ mock_clean_pxe):
+ token_path = self._create_token_file()
+
+ # set local boot
+ if is_localboot:
+ i_info = self.node.instance_info
+ i_info['capabilities'] = '{"boot_option": "local"}'
+ self.node.instance_info = i_info
+
+ self.node.power_state = states.POWER_ON
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+
+ boot_mode = None
+ is_whole_disk_image = True
+ disk_id = '0x12345678'
+ mock_deploy.return_value = disk_id
+
+ with task_manager.acquire(self.context, self.node.uuid) as task:
+ task.node.driver_internal_info['is_whole_disk_image'] = True
+ task.driver.vendor._continue_deploy(task, address='123456',
+ iqn='aaa-bbb',
+ key='fake-56789')
+
+ self.node.refresh()
+ self.assertEqual(states.ACTIVE, self.node.provision_state)
+ self.assertEqual(states.NOSTATE, self.node.target_provision_state)
+ self.assertEqual(states.POWER_ON, self.node.power_state)
self.assertIsNone(self.node.last_error)
self.assertFalse(os.path.exists(token_path))
mock_image_cache.assert_called_once_with()
@@ -754,8 +908,10 @@ class PXEDriverTestCase(db_base.DbTestCase):
mock_clean_pxe.assert_called_once_with(mock.ANY)
self.assertFalse(mock_switch_config.called)
else:
- mock_switch_config.assert_called_once_with(
- pxe_config_path, root_uuid, boot_mode)
+ mock_switch_config.assert_called_once_with(pxe_config_path,
+ disk_id,
+ boot_mode,
+ is_whole_disk_image)
self.assertFalse(mock_node_boot_dev.called)
self.assertFalse(mock_clean_pxe.called)
@@ -765,6 +921,12 @@ class PXEDriverTestCase(db_base.DbTestCase):
def test_continue_deploy_localboot(self):
self._test_continue_deploy(True)
+ def test_continue_deploy_whole_disk_image(self):
+ self._test_continue_deploy_whole_disk_image(False)
+
+ def test_continue_deploy_whole_disk_image_localboot(self):
+ self._test_continue_deploy_whole_disk_image(True)
+
def test_continue_deploy_invalid(self):
self.node.power_state = states.POWER_ON
self.node.provision_state = states.AVAILABLE
@@ -821,10 +983,12 @@ class CleanUpTestCase(db_base.DbTestCase):
mgr_utils.mock_the_extension_manager(driver="fake_pxe")
instance_info = INST_INFO_DICT
instance_info['deploy_key'] = 'fake-56789'
- self.node = obj_utils.create_test_node(self.context,
- driver='fake_pxe',
- instance_info=instance_info,
- driver_info=DRV_INFO_DICT)
+ self.node = obj_utils.create_test_node(
+ self.context, driver='fake_pxe',
+ instance_info=instance_info,
+ driver_info=DRV_INFO_DICT,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
def test_clean_up(self, mock_image_info, mock_cache, mock_pxe_clean,
mock_iscsi_clean, mock_unlink):
@@ -866,10 +1030,12 @@ class CleanUpFullFlowTestCase(db_base.DbTestCase):
mgr_utils.mock_the_extension_manager(driver="fake_pxe")
instance_info = INST_INFO_DICT
instance_info['deploy_key'] = 'fake-56789'
- self.node = obj_utils.create_test_node(self.context,
- driver='fake_pxe',
- instance_info=instance_info,
- driver_info=DRV_INFO_DICT)
+ self.node = obj_utils.create_test_node(
+ self.context, driver='fake_pxe',
+ instance_info=instance_info,
+ driver_info=DRV_INFO_DICT,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
self.port = obj_utils.create_test_port(self.context,
node_id=self.node.id)
@@ -945,9 +1111,12 @@ class TestAgentVendorPassthru(db_base.DbTestCase):
mgr_utils.mock_the_extension_manager()
self.driver = driver_factory.get_driver("fake")
self.driver.vendor = pxe.VendorPassthru()
- self.node = obj_utils.create_test_node(self.context, driver='fake',
- instance_info=INST_INFO_DICT,
- driver_info=DRV_INFO_DICT)
+ self.node = obj_utils.create_test_node(
+ self.context, driver='fake',
+ instance_info=INST_INFO_DICT,
+ driver_info=DRV_INFO_DICT,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
self.node.driver_internal_info['agent_url'] = 'http://1.2.3.4:1234'
self.task = mock.Mock(spec=task_manager.TaskManager)
self.task.shared = False
@@ -966,7 +1135,6 @@ class TestAgentVendorPassthru(db_base.DbTestCase):
reboot_and_finish_deploy_mock):
do_agent_iscsi_deploy_mock.return_value = 'some-root-uuid'
-
self.driver.vendor.continue_deploy(self.task)
destroy_token_file_mock.assert_called_once_with(self.node)
do_agent_iscsi_deploy_mock.assert_called_once_with(
@@ -974,7 +1142,7 @@ class TestAgentVendorPassthru(db_base.DbTestCase):
tftp_config = '/tftpboot/%s/config' % self.node.uuid
switch_pxe_config_mock.assert_called_once_with(tftp_config,
'some-root-uuid',
- None)
+ None, False)
reboot_and_finish_deploy_mock.assert_called_once_with(self.task)
@mock.patch.object(agent_base_vendor.BaseAgentVendor,
diff --git a/ironic/tests/test_images.py b/ironic/tests/test_images.py
index 60b706dea..547ed2024 100644
--- a/ironic/tests/test_images.py
+++ b/ironic/tests/test_images.py
@@ -25,6 +25,7 @@ from oslo_config import cfg
import six.moves.builtins as __builtin__
from ironic.common import exception
+from ironic.common.glance_service import service_utils as glance_utils
from ironic.common import image_service
from ironic.common import images
from ironic.common import utils
@@ -214,6 +215,68 @@ class IronicImagesTestCase(base.TestCase):
qemu_img_info_mock.assert_called_once_with('path')
self.assertEqual(1, size)
+ @mock.patch.object(images, 'get_image_properties')
+ @mock.patch.object(glance_utils, 'is_glance_image')
+ def test_is_whole_disk_image_no_img_src(self, mock_igi, mock_gip):
+ instance_info = {'image_source': ''}
+ iwdi = images.is_whole_disk_image('context', instance_info)
+ self.assertIsNone(iwdi)
+ self.assertFalse(mock_igi.called)
+ self.assertFalse(mock_gip.called)
+
+ @mock.patch.object(images, 'get_image_properties')
+ @mock.patch.object(glance_utils, 'is_glance_image')
+ def test_is_whole_disk_image_partition_image(self, mock_igi, mock_gip):
+ mock_igi.return_value = True
+ mock_gip.return_value = {'kernel_id': 'kernel',
+ 'ramdisk_id': 'ramdisk'}
+ instance_info = {'image_source': 'glance://partition_image'}
+ image_source = instance_info['image_source']
+ is_whole_disk_image = images.is_whole_disk_image('context',
+ instance_info)
+ self.assertFalse(is_whole_disk_image)
+ mock_igi.assert_called_once_with(image_source)
+ mock_gip.assert_called_once_with('context', image_source)
+
+ @mock.patch.object(images, 'get_image_properties')
+ @mock.patch.object(glance_utils, 'is_glance_image')
+ def test_is_whole_disk_image_whole_disk_image(self, mock_igi, mock_gip):
+ mock_igi.return_value = True
+ mock_gip.return_value = {}
+ instance_info = {'image_source': 'glance://whole_disk_image'}
+ image_source = instance_info['image_source']
+ is_whole_disk_image = images.is_whole_disk_image('context',
+ instance_info)
+ self.assertTrue(is_whole_disk_image)
+ mock_igi.assert_called_once_with(image_source)
+ mock_gip.assert_called_once_with('context', image_source)
+
+ @mock.patch.object(images, 'get_image_properties')
+ @mock.patch.object(glance_utils, 'is_glance_image')
+ def test_is_whole_disk_image_partition_non_glance(self, mock_igi,
+ mock_gip):
+ mock_igi.return_value = False
+ instance_info = {'image_source': 'partition_image',
+ 'kernel': 'kernel',
+ 'ramdisk': 'ramdisk'}
+ is_whole_disk_image = images.is_whole_disk_image('context',
+ instance_info)
+ self.assertFalse(is_whole_disk_image)
+ self.assertFalse(mock_gip.called)
+ mock_igi.assert_called_once_with(instance_info['image_source'])
+
+ @mock.patch.object(images, 'get_image_properties')
+ @mock.patch.object(glance_utils, 'is_glance_image')
+ def test_is_whole_disk_image_whole_disk_non_glance(self, mock_igi,
+ mock_gip):
+ mock_igi.return_value = False
+ instance_info = {'image_source': 'whole_disk_image'}
+ is_whole_disk_image = images.is_whole_disk_image('context',
+ instance_info)
+ self.assertTrue(is_whole_disk_image)
+ self.assertFalse(mock_gip.called)
+ mock_igi.assert_called_once_with(instance_info['image_source'])
+
class FsImageTestCase(base.TestCase):