summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFaizan Barmawer <faizan.barmawer@gmail.com>2015-02-16 05:42:08 -0800
committerNisha Agarwal <agarwalnisha1980@gmail.com>2016-03-21 16:12:08 +0000
commit644a1c36b444fadbdda6ae9f0174ed6da385111a (patch)
treee45d2a80677d5776af2aab49cf1d71170950d6e8
parent6d1b7c6d0df2b6e64ec944f85837460a219d472e (diff)
downloadironic-644a1c36b444fadbdda6ae9f0174ed6da385111a.tar.gz
Add support for partition images in agent drivers
This patch enables the partition image support for agent drivers which support `boot` iterface. Partial-Bug: 1526289 Co-Authored-By: Nisha Agarwal <agarwalnisha1980@gmail.com> Depends-on: I22bc29a39bf5c35f3eecb6d4e51cebd6aee0ce1 Depends-on: I37908470484744bb720f741d378106d1cb1227a3 Change-Id: Ifc8ba098f13b6fde712a584798fceb0321137bc9
-rw-r--r--ironic/drivers/modules/agent.py78
-rw-r--r--ironic/drivers/modules/agent_base_vendor.py24
-rw-r--r--ironic/drivers/modules/agent_config.template8
-rw-r--r--ironic/drivers/modules/deploy_utils.py111
-rw-r--r--ironic/drivers/modules/iscsi_deploy.py140
-rw-r--r--ironic/tests/unit/common/test_pxe_utils.py4
-rw-r--r--ironic/tests/unit/drivers/agent_pxe_config.template9
-rw-r--r--ironic/tests/unit/drivers/modules/test_agent.py348
-rw-r--r--ironic/tests/unit/drivers/modules/test_agent_base_vendor.py88
-rw-r--r--ironic/tests/unit/drivers/modules/test_iscsi_deploy.py48
-rw-r--r--releasenotes/notes/agent_partition_image-48a03700f41a3980.yaml4
11 files changed, 675 insertions, 187 deletions
diff --git a/ironic/drivers/modules/agent.py b/ironic/drivers/modules/agent.py
index e8c04f301..65aba00c5 100644
--- a/ironic/drivers/modules/agent.py
+++ b/ironic/drivers/modules/agent.py
@@ -111,6 +111,11 @@ OPTIONAL_PROPERTIES = {
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
+PARTITION_IMAGE_LABELS = ('kernel', 'ramdisk', 'root_gb', 'root_mb', 'swap_mb',
+ 'ephemeral_mb', 'ephemeral_format', 'configdrive',
+ 'preserve_ephemeral', 'image_type',
+ 'deploy_boot_mode')
+
def build_instance_info_for_deploy(task):
"""Build instance_info necessary for deploying to a node.
@@ -123,7 +128,7 @@ def build_instance_info_for_deploy(task):
"""
node = task.node
instance_info = node.instance_info
-
+ iwdi = node.driver_internal_info.get('is_whole_disk_image')
image_source = instance_info['image_source']
if service_utils.is_glance_image(image_source):
glance = image_service.GlanceImageService(version=2,
@@ -137,6 +142,10 @@ def build_instance_info_for_deploy(task):
instance_info['image_disk_format'] = image_info['disk_format']
instance_info['image_container_format'] = (
image_info['container_format'])
+
+ if not iwdi:
+ instance_info['kernel'] = image_info['properties']['kernel_id']
+ instance_info['ramdisk'] = image_info['properties']['ramdisk_id']
else:
try:
image_service.HttpImageService().validate_href(image_source)
@@ -148,6 +157,12 @@ def build_instance_info_for_deploy(task):
"is not reachable."), image_source)
instance_info['image_url'] = image_source
+ if not iwdi:
+ instance_info['image_type'] = 'partition'
+ i_info = deploy_utils.parse_instance_info(node)
+ instance_info.update(i_info)
+ else:
+ instance_info['image_type'] = 'whole-disk-image'
return instance_info
@@ -256,6 +271,7 @@ class AgentDeploy(base.DeployInterface):
params['instance_info.image_source'] = image_source
error_msg = _('Node %s failed to validate deploy image info. Some '
'parameters were missing') % node.uuid
+
deploy_utils.check_for_missing_params(params, error_msg)
if not service_utils.is_glance_image(image_source):
@@ -265,15 +281,6 @@ class AgentDeploy(base.DeployInterface):
"instance_info for node %s") % node.uuid)
check_image_size(task, image_source)
- is_whole_disk_image = node.driver_internal_info.get(
- 'is_whole_disk_image')
- # TODO(sirushtim): Remove once IPA has support for partition images.
- if is_whole_disk_image is False:
- raise exception.InvalidParameterValue(_(
- "Node %(node)s is configured to use the %(driver)s driver "
- "which currently does not support deploying partition "
- "images.") % {'node': node.uuid, 'driver': node.driver})
-
# Validate the root device hints
deploy_utils.parse_root_device_hints(node)
@@ -468,11 +475,44 @@ class AgentVendorInterface(agent_base_vendor.BaseAgentVendor):
if no_proxy is not None:
image_info['no_proxy'] = no_proxy
+ iwdi = node.driver_internal_info.get('is_whole_disk_image')
+ if not iwdi:
+ for label in PARTITION_IMAGE_LABELS:
+ image_info[label] = node.instance_info.get(label)
+ boot_option = deploy_utils.get_boot_option(node)
+ boot_mode = deploy_utils.get_boot_mode_for_deploy(node)
+ if boot_mode:
+ image_info['deploy_boot_mode'] = boot_mode
+ else:
+ image_info['deploy_boot_mode'] = 'bios'
+ image_info['boot_option'] = boot_option
+
# Tell the client to download and write the image with the given args
self._client.prepare_image(node, image_info)
task.process_event('wait')
+ def _get_uuid_from_result(self, task, type_uuid):
+ command = self._client.get_commands_status(task.node)[-1]
+
+ if command['command_result'] is not None:
+ words = command['command_result']['result'].split()
+ for word in words:
+ if type_uuid in word:
+ result = word.split('=')[1]
+ if not result:
+ msg = (_('Command result did not return %(type_uuid)s '
+ 'for node %(node)s. The version of the IPA '
+ 'ramdisk used in the deployment might not '
+ 'have support for provisioning of '
+ 'partition images.') %
+ {'type_uuid': type_uuid,
+ 'node': task.node.uuid})
+ LOG.error(msg)
+ deploy_utils.set_failed_state(task, msg)
+ return
+ return result
+
def check_deploy_success(self, node):
# should only ever be called after we've validated that
# the prepare_image command is complete
@@ -483,6 +523,7 @@ class AgentVendorInterface(agent_base_vendor.BaseAgentVendor):
def reboot_to_instance(self, task, **kwargs):
task.process_event('resume')
node = task.node
+ iwdi = task.node.driver_internal_info.get('is_whole_disk_image')
error = self.check_deploy_success(node)
if error is not None:
# TODO(jimrollenhagen) power off if using neutron dhcp to
@@ -492,11 +533,22 @@ class AgentVendorInterface(agent_base_vendor.BaseAgentVendor):
LOG.error(msg)
deploy_utils.set_failed_state(task, msg)
return
-
+ if not iwdi:
+ root_uuid = self._get_uuid_from_result(task, 'root_uuid')
+ if deploy_utils.get_boot_mode_for_deploy(node) == 'uefi':
+ efi_sys_uuid = (
+ self._get_uuid_from_result(task,
+ 'efi_system_partition_uuid'))
+ else:
+ efi_sys_uuid = None
+ task.node.driver_internal_info['root_uuid_or_disk_id'] = root_uuid
+ task.node.save()
+ self.prepare_instance_to_boot(task, root_uuid, efi_sys_uuid)
LOG.info(_LI('Image successfully written to node %s'), node.uuid)
LOG.debug('Rebooting node %s to instance', node.uuid)
+ if iwdi:
+ manager_utils.node_set_boot_device(task, 'disk', persistent=True)
- manager_utils.node_set_boot_device(task, 'disk', persistent=True)
self.reboot_and_finish_deploy(task)
# NOTE(TheJulia): If we deployed a whole disk image, we
@@ -505,7 +557,7 @@ class AgentVendorInterface(agent_base_vendor.BaseAgentVendor):
# TODO(rameshg87): Not all in-tree drivers using reboot_to_instance
# have a boot interface. So include a check for now. Remove this
# check once all in-tree drivers have a boot interface.
- if task.driver.boot:
+ if task.driver.boot and iwdi:
task.driver.boot.clean_up_ramdisk(task)
diff --git a/ironic/drivers/modules/agent_base_vendor.py b/ironic/drivers/modules/agent_base_vendor.py
index 7b6758ec1..bf10d1a35 100644
--- a/ironic/drivers/modules/agent_base_vendor.py
+++ b/ironic/drivers/modules/agent_base_vendor.py
@@ -710,6 +710,30 @@ class BaseAgentVendor(base.VendorInterface):
task.process_event('done')
LOG.info(_LI('Deployment to node %s done'), task.node.uuid)
+ def prepare_instance_to_boot(self, task, root_uuid, efi_sys_uuid):
+ """Prepares instance to boot.
+
+ :param task: a TaskManager object containing the node
+ :param root_uuid: the UUID for root partition
+ :param efi_sys_uuid: the UUID for the efi partition
+ :raises: InvalidState if fails to prepare instance
+ """
+
+ node = task.node
+ if deploy_utils.get_boot_option(node) == "local":
+ # Install the boot loader
+ self.configure_local_boot(
+ task, root_uuid=root_uuid,
+ efi_system_part_uuid=efi_sys_uuid)
+ try:
+ task.driver.boot.prepare_instance(task)
+ except Exception as e:
+ LOG.error(_LE('Deploy failed for instance %(instance)s. '
+ 'Error: %(error)s'),
+ {'instance': node.instance_uuid, 'error': e})
+ msg = _('Failed to continue agent deployment.')
+ self._log_and_raise_deployment_error(task, msg)
+
def configure_local_boot(self, task, root_uuid=None,
efi_system_part_uuid=None):
"""Helper method to configure local boot on the node.
diff --git a/ironic/drivers/modules/agent_config.template b/ironic/drivers/modules/agent_config.template
index 5c219cacb..cf1a87158 100644
--- a/ironic/drivers/modules/agent_config.template
+++ b/ironic/drivers/modules/agent_config.template
@@ -3,3 +3,11 @@ default deploy
label deploy
kernel {{ pxe_options.deployment_aki_path }}
append initrd={{ pxe_options.deployment_ari_path }} text {{ pxe_options.pxe_append_params }} ipa-api-url={{ pxe_options['ipa-api-url'] }} ipa-driver-name={{ pxe_options['ipa-driver-name'] }}{% if pxe_options.root_device %} root_device={{ pxe_options.root_device }}{% endif %} coreos.configdrive=0
+
+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/drivers/modules/deploy_utils.py b/ironic/drivers/modules/deploy_utils.py
index 88c69fc42..7b615a9b8 100644
--- a/ironic/drivers/modules/deploy_utils.py
+++ b/ironic/drivers/modules/deploy_utils.py
@@ -26,6 +26,7 @@ from oslo_config import cfg
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import excutils
+from oslo_utils import strutils
import six
from six.moves.urllib import parse
@@ -96,10 +97,12 @@ SUPPORTED_CAPABILITIES = {
'disk_label': ('msdos', 'gpt'),
}
+DISK_LAYOUT_PARAMS = ('root_gb', 'swap_mb', 'ephemeral_gb')
# All functions are called from deploy() directly or indirectly.
# They are split for stub-out.
+
def discovery(portal_address, portal_port):
"""Do iSCSI discovery on portal."""
utils.execute('iscsiadm',
@@ -1084,3 +1087,111 @@ def get_image_instance_info(node):
check_for_missing_params(info, error_msg)
return info
+
+
+def parse_instance_info(node):
+ """Gets the instance specific Node deployment info.
+
+ This method validates whether the 'instance_info' property of the
+ supplied node contains the required information for this driver to
+ deploy images to the node.
+
+ :param node: a single Node.
+ :returns: A dict with the instance_info values.
+ :raises: MissingParameterValue, if any of the required parameters are
+ missing.
+ :raises: InvalidParameterValue, if any of the parameters have invalid
+ value.
+ """
+
+ info = node.instance_info
+ i_info = {}
+ i_info['image_source'] = info.get('image_source')
+ iwdi = node.driver_internal_info.get('is_whole_disk_image')
+ if not iwdi:
+ if (i_info['image_source'] and
+ not service_utils.is_glance_image(
+ i_info['image_source'])):
+ i_info['kernel'] = info.get('kernel')
+ i_info['ramdisk'] = info.get('ramdisk')
+ i_info['root_gb'] = info.get('root_gb')
+
+ error_msg = _("Cannot validate driver deploy. Some parameters were missing"
+ " in node's instance_info")
+ check_for_missing_params(i_info, error_msg)
+
+ # Internal use only
+ i_info['deploy_key'] = info.get('deploy_key')
+ i_info['swap_mb'] = int(info.get('swap_mb', 0))
+ i_info['ephemeral_gb'] = info.get('ephemeral_gb', 0)
+ err_msg_invalid = _("Cannot validate parameter for driver deploy. "
+ "Invalid parameter %(param)s. Reason: %(reason)s")
+ for param in DISK_LAYOUT_PARAMS:
+ try:
+ int(i_info[param])
+ except ValueError:
+ reason = _("%s is not an integer value.") % i_info[param]
+ raise exception.InvalidParameterValue(err_msg_invalid %
+ {'param': param,
+ 'reason': reason})
+
+ i_info['root_mb'] = 1024 * int(info.get('root_gb'))
+
+ if iwdi:
+ 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)
+ 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
+
+ preserve_ephemeral = info.get('preserve_ephemeral', False)
+ try:
+ i_info['preserve_ephemeral'] = (
+ strutils.bool_from_string(preserve_ephemeral, strict=True))
+ except ValueError as e:
+ raise exception.InvalidParameterValue(
+ err_msg_invalid % {'param': 'preserve_ephemeral', 'reason': e})
+
+ # NOTE(Zhenguo): If rebuilding with preserve_ephemeral option, check
+ # that the disk layout is unchanged.
+ if i_info['preserve_ephemeral']:
+ _check_disk_layout_unchanged(node, i_info)
+
+ return i_info
+
+
+def _check_disk_layout_unchanged(node, i_info):
+ """Check whether disk layout is unchanged.
+
+ If the node has already been deployed to, this checks whether the disk
+ layout for the node is the same as when it had been deployed to.
+
+ :param node: the node of interest
+ :param i_info: instance information (a dictionary) for the node, containing
+ disk layout information
+ :raises: InvalidParameterValue if the disk layout changed
+ """
+ # If a node has been deployed to, this is the instance information
+ # used for that deployment.
+ driver_internal_info = node.driver_internal_info
+ if 'instance' not in driver_internal_info:
+ return
+
+ error_msg = ''
+ for param in DISK_LAYOUT_PARAMS:
+ param_value = int(driver_internal_info['instance'][param])
+ if param_value != int(i_info[param]):
+ error_msg += (_(' Deployed value of %(param)s was %(param_value)s '
+ 'but requested value is %(request_value)s.') %
+ {'param': param, 'param_value': param_value,
+ 'request_value': i_info[param]})
+
+ if error_msg:
+ err_msg_invalid = _("The following parameters have different values "
+ "from previous deployment:%(error_msg)s")
+ raise exception.InvalidParameterValue(err_msg_invalid %
+ {'error_msg': error_msg})
diff --git a/ironic/drivers/modules/iscsi_deploy.py b/ironic/drivers/modules/iscsi_deploy.py
index a7a464830..0206b738d 100644
--- a/ironic/drivers/modules/iscsi_deploy.py
+++ b/ironic/drivers/modules/iscsi_deploy.py
@@ -20,12 +20,10 @@ from ironic_lib import utils as ironic_utils
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import fileutils
-from oslo_utils import strutils
from six.moves.urllib import parse
from ironic.common import dhcp_factory
from ironic.common import exception
-from ironic.common.glance_service import service_utils as glance_service_utils
from ironic.common.i18n import _
from ironic.common.i18n import _LE
from ironic.common.i18n import _LI
@@ -107,39 +105,6 @@ def _get_image_file_path(node_uuid):
return os.path.join(_get_image_dir_path(node_uuid), 'disk')
-def _check_disk_layout_unchanged(node, i_info):
- """Check whether disk layout is unchanged.
-
- If the node has already been deployed to, this checks whether the disk
- layout for the node is the same as when it had been deployed to.
-
- :param node: the node of interest
- :param i_info: instance information (a dictionary) for the node, containing
- disk layout information
- :raises: InvalidParameterValue if the disk layout changed
- """
- # If a node has been deployed to, this is the instance information
- # used for that deployment.
- driver_internal_info = node.driver_internal_info
- if 'instance' not in driver_internal_info:
- return
-
- error_msg = ''
- for param in DISK_LAYOUT_PARAMS:
- param_value = int(driver_internal_info['instance'][param])
- if param_value != int(i_info[param]):
- error_msg += (_(' Deployed value of %(param)s was %(param_value)s '
- 'but requested value is %(request_value)s.') %
- {'param': param, 'param_value': param_value,
- 'request_value': i_info[param]})
-
- if error_msg:
- err_msg_invalid = _("The following parameters have different values "
- "from previous deployment:%(error_msg)s")
- raise exception.InvalidParameterValue(err_msg_invalid %
- {'error_msg': error_msg})
-
-
def _save_disk_layout(node, i_info):
"""Saves the disk layout.
@@ -159,81 +124,6 @@ def _save_disk_layout(node, i_info):
node.save()
-def parse_instance_info(node):
- """Gets the instance specific Node deployment info.
-
- This method validates whether the 'instance_info' property of the
- supplied node contains the required information for this driver to
- deploy images to the node.
-
- :param node: a single Node.
- :returns: A dict with the instance_info values.
- :raises: MissingParameterValue, if any of the required parameters are
- missing.
- :raises: InvalidParameterValue, if any of the parameters have invalid
- value.
- """
- info = node.instance_info
- i_info = {}
- i_info['image_source'] = info.get('image_source')
- 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['root_gb'] = info.get('root_gb')
-
- error_msg = _("Cannot validate iSCSI deploy. Some parameters were missing"
- " in node's instance_info")
- deploy_utils.check_for_missing_params(i_info, error_msg)
-
- # Internal use only
- i_info['deploy_key'] = info.get('deploy_key')
-
- i_info['swap_mb'] = info.get('swap_mb', 0)
- i_info['ephemeral_gb'] = info.get('ephemeral_gb', 0)
- err_msg_invalid = _("Cannot validate parameter for iSCSI deploy. "
- "Invalid parameter %(param)s. Reason: %(reason)s")
- for param in DISK_LAYOUT_PARAMS:
- try:
- int(i_info[param])
- except ValueError:
- reason = _("%s is not an integer value.") % i_info[param]
- raise exception.InvalidParameterValue(err_msg_invalid %
- {'param': param,
- 'reason': reason})
-
- if 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
-
- preserve_ephemeral = info.get('preserve_ephemeral', False)
- try:
- i_info['preserve_ephemeral'] = (
- strutils.bool_from_string(preserve_ephemeral, strict=True))
- except ValueError as e:
- raise exception.InvalidParameterValue(
- err_msg_invalid % {'param': 'preserve_ephemeral', 'reason': e})
-
- # NOTE(Zhenguo): If rebuilding with preserve_ephemeral option, check
- # that the disk layout is unchanged.
- if i_info['preserve_ephemeral']:
- _check_disk_layout_unchanged(node, i_info)
-
- return i_info
-
-
def check_image_size(task):
"""Check if the requested image is larger than the root partition size.
@@ -241,7 +131,7 @@ def check_image_size(task):
:raises: InstanceDeployFailure if size of the image is greater than root
partition.
"""
- i_info = parse_instance_info(task.node)
+ i_info = deploy_utils.parse_instance_info(task.node)
image_path = _get_image_file_path(task.node.uuid)
image_mb = disk_utils.get_image_mb(image_path)
root_mb = 1024 * int(i_info['root_gb'])
@@ -263,7 +153,7 @@ def cache_instance_image(ctx, node):
:returns: a tuple containing the uuid of the image and the path in
the filesystem where image is cached.
"""
- i_info = parse_instance_info(node)
+ i_info = deploy_utils.parse_instance_info(node)
fileutils.ensure_tree(_get_image_dir_path(node.uuid))
image_path = _get_image_file_path(node.uuid)
uuid = i_info['image_source']
@@ -298,7 +188,7 @@ def get_deploy_info(node, **kwargs):
value.
"""
deploy_key = kwargs.get('key')
- i_info = parse_instance_info(node)
+ i_info = deploy_utils.parse_instance_info(node)
if i_info['deploy_key'] != deploy_key:
raise exception.InvalidParameterValue(_("Deploy key does not match"))
@@ -410,7 +300,7 @@ def continue_deploy(task, **kwargs):
if params.get('preserve_ephemeral', False):
# Save disk layout information, to check that they are unchanged
# for any future rebuilds
- _save_disk_layout(node, parse_instance_info(node))
+ _save_disk_layout(node, deploy_utils.parse_instance_info(node))
destroy_images(node.uuid)
return uuid_dict_returned
@@ -562,7 +452,7 @@ def validate(task):
# Validate the root device hints
deploy_utils.parse_root_device_hints(task.node)
- parse_instance_info(task.node)
+ deploy_utils.parse_instance_info(task.node)
def validate_pass_bootloader_info_input(task, input_params):
@@ -972,21 +862,7 @@ class VendorPassthru(agent_base_vendor.BaseAgentVendor):
LOG.debug('Continuing the deployment on node %s', node.uuid)
uuid_dict_returned = do_agent_iscsi_deploy(task, self._client)
-
- if deploy_utils.get_boot_option(node) == "local":
- # Install the boot loader
- root_uuid = uuid_dict_returned.get('root uuid')
- efi_sys_uuid = uuid_dict_returned.get('efi system partition uuid')
- self.configure_local_boot(
- task, root_uuid=root_uuid,
- efi_system_part_uuid=efi_sys_uuid)
-
- try:
- task.driver.boot.prepare_instance(task)
- except Exception as e:
- LOG.error(_LE('Deploy failed for instance %(instance)s. '
- 'Error: %(error)s'),
- {'instance': node.instance_uuid, 'error': e})
- msg = _('Failed to continue agent deployment.')
- deploy_utils.set_failed_state(task, msg)
+ root_uuid = uuid_dict_returned.get('root uuid')
+ efi_sys_uuid = uuid_dict_returned.get('efi system partition uuid')
+ self.prepare_instance_to_boot(task, root_uuid, efi_sys_uuid)
self.reboot_and_finish_deploy(task)
diff --git a/ironic/tests/unit/common/test_pxe_utils.py b/ironic/tests/unit/common/test_pxe_utils.py
index bb584c92d..6f73caaa7 100644
--- a/ironic/tests/unit/common/test_pxe_utils.py
+++ b/ironic/tests/unit/common/test_pxe_utils.py
@@ -40,6 +40,8 @@ class TestPXEUtils(db_base.DbTestCase):
u'c02d7f33c123/deploy_kernel',
'aki_path': u'/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/'
u'kernel',
+ 'ari_path': u'/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/'
+ u'ramdisk',
'pxe_append_params': 'test_param',
'deployment_ari_path': u'/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7'
u'f33c123/deploy_ramdisk',
@@ -50,8 +52,6 @@ class TestPXEUtils(db_base.DbTestCase):
self.pxe_options = {
'deployment_key': '0123456789ABCDEFGHIJKLMNOPQRSTUV',
- 'ari_path': u'/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/'
- u'ramdisk',
'iscsi_target_iqn': u'iqn-1be26c0b-03f2-4d2e-ae87-c02d7f33'
u'c123',
'deployment_id': u'1be26c0b-03f2-4d2e-ae87-c02d7f33c123',
diff --git a/ironic/tests/unit/drivers/agent_pxe_config.template b/ironic/tests/unit/drivers/agent_pxe_config.template
index 7b26d58cf..347d05c1b 100644
--- a/ironic/tests/unit/drivers/agent_pxe_config.template
+++ b/ironic/tests/unit/drivers/agent_pxe_config.template
@@ -3,3 +3,12 @@ default deploy
label deploy
kernel /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_kernel
append initrd=/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_ramdisk text test_param ipa-api-url=http://192.168.122.184:6385 ipa-driver-name=agent_ipmitool root_device=vendor=fake,size=123 coreos.configdrive=0
+
+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/unit/drivers/modules/test_agent.py b/ironic/tests/unit/drivers/modules/test_agent.py
index 595322b2a..147612e3e 100644
--- a/ironic/tests/unit/drivers/modules/test_agent.py
+++ b/ironic/tests/unit/drivers/modules/test_agent.py
@@ -55,6 +55,9 @@ class TestAgentMethods(db_base.DbTestCase):
def test_build_instance_info_for_deploy_glance_image(self, glance_mock):
i_info = self.node.instance_info
i_info['image_source'] = '733d1c44-a2ea-414b-aca7-69decf20d810'
+ driver_internal_info = self.node.driver_internal_info
+ driver_internal_info['is_whole_disk_image'] = True
+ self.node.driver_internal_info = driver_internal_info
self.node.instance_info = i_info
self.node.save()
@@ -76,14 +79,82 @@ class TestAgentMethods(db_base.DbTestCase):
glance_mock.return_value.swift_temp_url.assert_called_once_with(
image_info)
+ @mock.patch.object(deploy_utils, 'parse_instance_info', autospec=True)
+ @mock.patch.object(image_service, 'GlanceImageService', autospec=True)
+ def test_build_instance_info_for_deploy_glance_partition_image(
+ self, glance_mock, parse_instance_info_mock):
+ i_info = self.node.instance_info
+ i_info['image_source'] = '733d1c44-a2ea-414b-aca7-69decf20d810'
+ i_info['kernel'] = '13ce5a56-1de3-4916-b8b2-be778645d003'
+ i_info['ramdisk'] = 'a5a370a8-1b39-433f-be63-2c7d708e4b4e'
+ i_info['root_gb'] = 5
+ i_info['swap_mb'] = 4
+ i_info['ephemeral_gb'] = 0
+ i_info['ephemeral_format'] = None
+ i_info['configdrive'] = 'configdrive'
+ driver_internal_info = self.node.driver_internal_info
+ driver_internal_info['is_whole_disk_image'] = False
+ self.node.driver_internal_info = driver_internal_info
+ self.node.instance_info = i_info
+ self.node.save()
+
+ image_info = {'checksum': 'aa', 'disk_format': 'qcow2',
+ 'container_format': 'bare',
+ 'properties': {'kernel_id': 'kernel',
+ 'ramdisk_id': 'ramdisk'}}
+ glance_mock.return_value.show = mock.MagicMock(spec_set=[],
+ return_value=image_info)
+ glance_obj_mock = glance_mock.return_value
+ glance_obj_mock.swift_temp_url.return_value = 'temp-url'
+ parse_instance_info_mock.return_value = {'swap_mb': 4}
+ image_source = '733d1c44-a2ea-414b-aca7-69decf20d810'
+ expected_i_info = {'root_gb': 5,
+ 'swap_mb': 4,
+ 'ephemeral_gb': 0,
+ 'ephemeral_format': None,
+ 'configdrive': 'configdrive',
+ 'image_source': image_source,
+ 'image_url': 'temp-url',
+ 'kernel': 'kernel',
+ 'ramdisk': 'ramdisk',
+ 'image_type': 'partition',
+ 'image_checksum': 'aa',
+ 'fake_password': 'fakepass',
+ 'image_container_format': 'bare',
+ 'image_disk_format': 'qcow2',
+ 'foo': 'bar'}
+ mgr_utils.mock_the_extension_manager(driver='fake_agent')
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+
+ info = agent.build_instance_info_for_deploy(task)
+
+ glance_mock.assert_called_once_with(version=2,
+ context=task.context)
+ glance_mock.return_value.show.assert_called_once_with(
+ self.node.instance_info['image_source'])
+ glance_mock.return_value.swift_temp_url.assert_called_once_with(
+ image_info)
+ image_type = task.node.instance_info.get('image_type')
+ self.assertEqual('partition', image_type)
+ self.assertEqual('kernel', info.get('kernel'))
+ self.assertEqual('ramdisk', info.get('ramdisk'))
+ self.assertEqual(expected_i_info, info)
+ parse_instance_info_mock.assert_called_once_with(task.node)
+
@mock.patch.object(image_service.HttpImageService, 'validate_href',
autospec=True)
def test_build_instance_info_for_deploy_nonglance_image(
self, validate_href_mock):
i_info = self.node.instance_info
+ driver_internal_info = self.node.driver_internal_info
i_info['image_source'] = 'http://image-ref'
i_info['image_checksum'] = 'aa'
+ i_info['root_gb'] = 10
+ i_info['image_checksum'] = 'aa'
+ driver_internal_info['is_whole_disk_image'] = True
self.node.instance_info = i_info
+ self.node.driver_internal_info = driver_internal_info
self.node.save()
mgr_utils.mock_the_extension_manager(driver='fake_agent')
@@ -97,6 +168,51 @@ class TestAgentMethods(db_base.DbTestCase):
validate_href_mock.assert_called_once_with(
mock.ANY, 'http://image-ref')
+ @mock.patch.object(deploy_utils, 'parse_instance_info', autospec=True)
+ @mock.patch.object(image_service.HttpImageService, 'validate_href',
+ autospec=True)
+ def test_build_instance_info_for_deploy_nonglance_partition_image(
+ self, validate_href_mock, parse_instance_info_mock):
+ i_info = self.node.instance_info
+ driver_internal_info = self.node.driver_internal_info
+ i_info['image_source'] = 'http://image-ref'
+ i_info['kernel'] = 'http://kernel-ref'
+ i_info['ramdisk'] = 'http://ramdisk-ref'
+ i_info['image_checksum'] = 'aa'
+ i_info['root_gb'] = 10
+ driver_internal_info['is_whole_disk_image'] = False
+ self.node.instance_info = i_info
+ self.node.driver_internal_info = driver_internal_info
+ self.node.save()
+
+ mgr_utils.mock_the_extension_manager(driver='fake_agent')
+ validate_href_mock.side_effect = ['http://image-ref',
+ 'http://kernel-ref',
+ 'http://ramdisk-ref']
+ parse_instance_info_mock.return_value = {'swap_mb': 5}
+ expected_i_info = {'image_source': 'http://image-ref',
+ 'image_url': 'http://image-ref',
+ 'image_type': 'partition',
+ 'kernel': 'http://kernel-ref',
+ 'ramdisk': 'http://ramdisk-ref',
+ 'image_checksum': 'aa',
+ 'root_gb': 10,
+ 'swap_mb': 5,
+ 'fake_password': 'fakepass',
+ 'foo': 'bar'}
+ with task_manager.acquire(
+ self.context, self.node.uuid, shared=False) as task:
+
+ info = agent.build_instance_info_for_deploy(task)
+
+ self.assertEqual(self.node.instance_info['image_source'],
+ info['image_url'])
+ validate_href_mock.assert_called_once_with(
+ mock.ANY, 'http://image-ref')
+ self.assertEqual('partition', info.get('image_type'))
+ self.assertEqual(expected_i_info, info)
+ parse_instance_info_mock.assert_called_once_with(task.node)
+
@mock.patch.object(image_service.HttpImageService, 'validate_href',
autospec=True)
def test_build_instance_info_for_deploy_nonsupported_image(
@@ -289,19 +405,6 @@ class TestAgentDeploy(db_base.DbTestCase):
@mock.patch.object(images, 'image_show', autospec=True)
@mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
- def test_validate_agent_fail_partition_image(
- self, pxe_boot_validate_mock, show_mock):
- with task_manager.acquire(
- self.context, self.node['uuid'], shared=False) as task:
- task.node.driver_internal_info['is_whole_disk_image'] = False
- self.assertRaises(exception.InvalidParameterValue,
- self.driver.validate, task)
- pxe_boot_validate_mock.assert_called_once_with(
- task.driver.boot, task)
- show_mock.assert_called_once_with(self.context, 'fake-image')
-
- @mock.patch.object(images, 'image_show', autospec=True)
- @mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
def test_validate_invalid_root_device_hints(
self, pxe_boot_validate_mock, show_mock):
with task_manager.acquire(self.context, self.node.uuid,
@@ -572,20 +675,80 @@ class TestAgentVendor(db_base.DbTestCase):
}
)
+ def test_continue_deploy_partition_image(self):
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ i_info = self.node.instance_info
+ i_info['kernel'] = 'kernel'
+ i_info['ramdisk'] = 'ramdisk'
+ i_info['root_gb'] = 10
+ i_info['swap_mb'] = 10
+ i_info['ephemeral_mb'] = 0
+ i_info['ephemeral_format'] = 'abc'
+ i_info['configdrive'] = 'configdrive'
+ i_info['preserve_ephemeral'] = False
+ i_info['image_type'] = 'partition'
+ i_info['root_mb'] = 10240
+ i_info['deploy_boot_mode'] = 'bios'
+ i_info['capabilities'] = '{"boot_option": "local"}'
+ self.node.instance_info = i_info
+ driver_internal_info = self.node.driver_internal_info
+ driver_internal_info['is_whole_disk_image'] = False
+ self.node.driver_internal_info = driver_internal_info
+ self.node.save()
+ test_temp_url = 'http://image'
+ expected_image_info = {
+ 'urls': [test_temp_url],
+ 'id': 'fake-image',
+ 'checksum': 'checksum',
+ 'disk_format': 'qcow2',
+ 'container_format': 'bare',
+ 'stream_raw_images': True,
+ 'kernel': 'kernel',
+ 'ramdisk': 'ramdisk',
+ 'root_gb': 10,
+ 'swap_mb': 10,
+ 'ephemeral_mb': 0,
+ 'ephemeral_format': 'abc',
+ 'configdrive': 'configdrive',
+ 'preserve_ephemeral': False,
+ 'image_type': 'partition',
+ 'root_mb': 10240,
+ 'boot_option': 'local',
+ 'deploy_boot_mode': 'bios'
+ }
+
+ client_mock = mock.MagicMock(spec_set=['prepare_image'])
+ self.passthru._client = client_mock
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ self.passthru.continue_deploy(task)
+
+ client_mock.prepare_image.assert_called_with(task.node,
+ expected_image_info)
+ self.assertEqual(states.DEPLOYWAIT, task.node.provision_state)
+ self.assertEqual(states.ACTIVE,
+ task.node.target_provision_state)
+
+ @mock.patch.object(agent.AgentVendorInterface, '_get_uuid_from_result',
+ autospec=True)
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
@mock.patch.object(fake.FakePower, 'get_power_state',
spec=types.FunctionType)
@mock.patch.object(agent_client.AgentClient, 'power_off',
spec=types.FunctionType)
- @mock.patch('ironic.conductor.utils.node_set_boot_device', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'prepare_instance',
+ autospec=True)
@mock.patch('ironic.drivers.modules.agent.AgentVendorInterface'
'.check_deploy_success', autospec=True)
@mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk', autospec=True)
def test_reboot_to_instance(self, clean_pxe_mock, check_deploy_mock,
- bootdev_mock, power_off_mock,
- get_power_state_mock, node_power_action_mock):
+ prepare_mock, power_off_mock,
+ get_power_state_mock, node_power_action_mock,
+ uuid_mock):
check_deploy_mock.return_value = None
-
+ uuid_mock.return_value = 'root_uuid'
self.node.provision_state = states.DEPLOYWAIT
self.node.target_provision_state = states.ACTIVE
self.node.save()
@@ -598,30 +761,128 @@ class TestAgentVendor(db_base.DbTestCase):
clean_pxe_mock.assert_called_once_with(task.driver.boot, task)
check_deploy_mock.assert_called_once_with(mock.ANY, task.node)
- bootdev_mock.assert_called_once_with(task, 'disk', persistent=True)
power_off_mock.assert_called_once_with(task.node)
get_power_state_mock.assert_called_once_with(task)
node_power_action_mock.assert_called_once_with(
task, states.REBOOT)
+ self.assertFalse(prepare_mock.called)
self.assertEqual(states.ACTIVE, task.node.provision_state)
self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+ driver_int_info = task.node.driver_internal_info
+ self.assertIsNone(driver_int_info.get('root_uuid_or_disk_id'))
+ self.assertFalse(uuid_mock.called)
+ @mock.patch.object(deploy_utils, 'get_boot_mode_for_deploy', autospec=True)
+ @mock.patch.object(agent.AgentVendorInterface, '_get_uuid_from_result',
+ autospec=True)
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
@mock.patch.object(fake.FakePower, 'get_power_state',
spec=types.FunctionType)
@mock.patch.object(agent_client.AgentClient, 'power_off',
spec=types.FunctionType)
- @mock.patch('ironic.conductor.utils.node_set_boot_device', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'prepare_instance',
+ autospec=True)
+ @mock.patch('ironic.drivers.modules.agent.AgentVendorInterface'
+ '.check_deploy_success', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk', autospec=True)
+ def test_reboot_to_instance_partition_image(self, clean_pxe_mock,
+ check_deploy_mock,
+ prepare_mock, power_off_mock,
+ get_power_state_mock,
+ node_power_action_mock,
+ uuid_mock, boot_mode_mock):
+ check_deploy_mock.return_value = None
+ uuid_mock.return_value = 'root_uuid'
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ boot_mode_mock.return_value = 'bios'
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ get_power_state_mock.return_value = states.POWER_OFF
+ task.node.driver_internal_info['is_whole_disk_image'] = False
+
+ self.passthru.reboot_to_instance(task)
+
+ self.assertFalse(clean_pxe_mock.called)
+ check_deploy_mock.assert_called_once_with(mock.ANY, task.node)
+ power_off_mock.assert_called_once_with(task.node)
+ get_power_state_mock.assert_called_once_with(task)
+ node_power_action_mock.assert_called_once_with(
+ task, states.REBOOT)
+ prepare_mock.assert_called_once_with(task.driver.boot, task)
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+ driver_int_info = task.node.driver_internal_info
+ self.assertEqual(driver_int_info.get('root_uuid_or_disk_id'),
+ 'root_uuid')
+ uuid_mock.assert_called_once_with(self.passthru, task, 'root_uuid')
+ boot_mode_mock.assert_called_once_with(task.node)
+
+ @mock.patch.object(agent.AgentVendorInterface, '_get_uuid_from_result',
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(fake.FakePower, 'get_power_state',
+ spec=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'power_off',
+ spec=types.FunctionType)
+ @mock.patch.object(pxe.PXEBoot, 'prepare_instance',
+ autospec=True)
@mock.patch('ironic.drivers.modules.agent.AgentVendorInterface'
'.check_deploy_success', autospec=True)
@mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk', autospec=True)
def test_reboot_to_instance_boot_none(self, clean_pxe_mock,
check_deploy_mock,
- bootdev_mock, power_off_mock,
+ prepare_mock, power_off_mock,
get_power_state_mock,
- node_power_action_mock):
+ node_power_action_mock,
+ uuid_mock):
check_deploy_mock.return_value = None
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ get_power_state_mock.return_value = states.POWER_OFF
+ task.node.driver_internal_info['is_whole_disk_image'] = True
+ task.driver.boot = None
+ self.passthru.reboot_to_instance(task)
+
+ self.assertFalse(clean_pxe_mock.called)
+ self.assertFalse(prepare_mock.called)
+ power_off_mock.assert_called_once_with(task.node)
+ check_deploy_mock.assert_called_once_with(mock.ANY, task.node)
+ driver_int_info = task.node.driver_internal_info
+ self.assertIsNone(driver_int_info.get('root_uuid_or_disk_id'))
+
+ get_power_state_mock.assert_called_once_with(task)
+ node_power_action_mock.assert_called_once_with(
+ task, states.REBOOT)
+ self.assertEqual(states.ACTIVE, task.node.provision_state)
+ self.assertEqual(states.NOSTATE, task.node.target_provision_state)
+ self.assertFalse(uuid_mock.called)
+
+ @mock.patch.object(agent.AgentVendorInterface, '_get_uuid_from_result',
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(fake.FakePower, 'get_power_state',
+ spec=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'power_off',
+ spec=types.FunctionType)
+ @mock.patch.object(pxe.PXEBoot, 'prepare_instance',
+ autospec=True)
+ @mock.patch('ironic.drivers.modules.agent.AgentVendorInterface'
+ '.check_deploy_success', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk', autospec=True)
+ def test_reboot_to_instance_boot_error(self, clean_pxe_mock,
+ check_deploy_mock,
+ prepare_mock, power_off_mock,
+ get_power_state_mock,
+ node_power_action_mock,
+ uuid_mock):
+ check_deploy_mock.return_value = "Error"
+ uuid_mock.return_value = None
self.node.provision_state = states.DEPLOYWAIT
self.node.target_provision_state = states.ACTIVE
self.node.save()
@@ -630,12 +891,55 @@ class TestAgentVendor(db_base.DbTestCase):
get_power_state_mock.return_value = states.POWER_OFF
task.node.driver_internal_info['is_whole_disk_image'] = True
task.driver.boot = None
+ self.passthru.reboot_to_instance(task)
+
+ self.assertFalse(clean_pxe_mock.called)
+ self.assertFalse(prepare_mock.called)
+ self.assertFalse(power_off_mock.called)
+ check_deploy_mock.assert_called_once_with(mock.ANY, task.node)
+ self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
+ self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'configure_local_boot', autospec=True)
+ @mock.patch.object(deploy_utils, 'try_set_boot_device', autospec=True)
+ @mock.patch.object(agent.AgentVendorInterface, '_get_uuid_from_result',
+ autospec=True)
+ @mock.patch.object(manager_utils, 'node_power_action', autospec=True)
+ @mock.patch.object(fake.FakePower, 'get_power_state',
+ spec=types.FunctionType)
+ @mock.patch.object(agent_client.AgentClient, 'power_off',
+ spec=types.FunctionType)
+ @mock.patch.object(pxe.PXEBoot, 'prepare_instance',
+ autospec=True)
+ @mock.patch('ironic.drivers.modules.agent.AgentVendorInterface'
+ '.check_deploy_success', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk', autospec=True)
+ def test_reboot_to_instance_localboot(self, clean_pxe_mock,
+ check_deploy_mock,
+ prepare_mock, power_off_mock,
+ get_power_state_mock,
+ node_power_action_mock,
+ uuid_mock,
+ bootdev_mock,
+ configure_mock):
+ check_deploy_mock.return_value = None
+ uuid_mock.side_effect = ['root_uuid', 'efi_uuid']
+ self.node.provision_state = states.DEPLOYWAIT
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ get_power_state_mock.return_value = states.POWER_OFF
+ task.node.driver_internal_info['is_whole_disk_image'] = False
+ boot_option = {'capabilities': '{"boot_option": "local"}'}
+ task.node.instance_info = boot_option
self.passthru.reboot_to_instance(task)
self.assertFalse(clean_pxe_mock.called)
check_deploy_mock.assert_called_once_with(mock.ANY, task.node)
- bootdev_mock.assert_called_once_with(task, 'disk', persistent=True)
+ self.assertFalse(bootdev_mock.called)
power_off_mock.assert_called_once_with(task.node)
get_power_state_mock.assert_called_once_with(task)
node_power_action_mock.assert_called_once_with(
diff --git a/ironic/tests/unit/drivers/modules/test_agent_base_vendor.py b/ironic/tests/unit/drivers/modules/test_agent_base_vendor.py
index 63227c84e..b4c1ded17 100644
--- a/ironic/tests/unit/drivers/modules/test_agent_base_vendor.py
+++ b/ironic/tests/unit/drivers/modules/test_agent_base_vendor.py
@@ -29,6 +29,7 @@ from ironic.drivers.modules import agent_base_vendor
from ironic.drivers.modules import agent_client
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules import fake
+from ironic.drivers.modules import pxe
from ironic import objects
from ironic.tests.unit.conductor import mgr_utils
from ironic.tests.unit.db import base as db_base
@@ -791,6 +792,93 @@ class TestBaseAgentVendor(db_base.DbTestCase):
self.assertEqual(states.DEPLOYFAIL, task.node.provision_state)
self.assertEqual(states.ACTIVE, task.node.target_provision_state)
+ @mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True)
+ @mock.patch.object(deploy_utils, 'get_boot_option', autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'configure_local_boot', autospec=True)
+ def test_prepare_instance_to_boot_netboot(self, configure_mock,
+ boot_option_mock,
+ prepare_instance_mock,
+ failed_state_mock):
+ boot_option_mock.return_value = 'netboot'
+ prepare_instance_mock.return_value = None
+ self.node.provision_state = states.DEPLOYING
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ root_uuid = 'root_uuid'
+ efi_system_part_uuid = 'efi_sys_uuid'
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.passthru.prepare_instance_to_boot(task, root_uuid,
+ efi_system_part_uuid)
+ self.assertFalse(configure_mock.called)
+ boot_option_mock.assert_called_once_with(task.node)
+ prepare_instance_mock.assert_called_once_with(task.driver.boot,
+ task)
+ self.assertFalse(failed_state_mock.called)
+
+ @mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True)
+ @mock.patch.object(deploy_utils, 'get_boot_option', autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'configure_local_boot', autospec=True)
+ def test_prepare_instance_to_boot_localboot(self, configure_mock,
+ boot_option_mock,
+ prepare_instance_mock,
+ failed_state_mock):
+ boot_option_mock.return_value = 'local'
+ prepare_instance_mock.return_value = None
+ self.node.provision_state = states.DEPLOYING
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ root_uuid = 'root_uuid'
+ efi_system_part_uuid = 'efi_sys_uuid'
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.passthru.prepare_instance_to_boot(task, root_uuid,
+ efi_system_part_uuid)
+ configure_mock.assert_called_once_with(self.passthru, task,
+ root_uuid,
+ efi_system_part_uuid)
+ boot_option_mock.assert_called_once_with(task.node)
+ prepare_instance_mock.assert_called_once_with(task.driver.boot,
+ task)
+ self.assertFalse(failed_state_mock.called)
+
+ @mock.patch.object(deploy_utils, 'set_failed_state', autospec=True)
+ @mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True)
+ @mock.patch.object(deploy_utils, 'get_boot_option', autospec=True)
+ @mock.patch.object(agent_base_vendor.BaseAgentVendor,
+ 'configure_local_boot', autospec=True)
+ def test_prepare_instance_to_boot_configure_fails(self, configure_mock,
+ boot_option_mock,
+ prepare_mock,
+ failed_state_mock):
+ boot_option_mock.return_value = 'local'
+ self.node.provision_state = states.DEPLOYING
+ self.node.target_provision_state = states.ACTIVE
+ self.node.save()
+ root_uuid = 'root_uuid'
+ efi_system_part_uuid = 'efi_sys_uuid'
+ reason = 'reason'
+ configure_mock.side_effect = (
+ exception.InstanceDeployFailure(reason=reason))
+ prepare_mock.side_effect = (
+ exception.InstanceDeployFailure(reason=reason))
+
+ with task_manager.acquire(self.context, self.node['uuid'],
+ shared=False) as task:
+ self.assertRaises(exception.InstanceDeployFailure,
+ self.passthru.prepare_instance_to_boot, task,
+ root_uuid, efi_system_part_uuid)
+ configure_mock.assert_called_once_with(self.passthru, task,
+ root_uuid,
+ efi_system_part_uuid)
+ boot_option_mock.assert_called_once_with(task.node)
+ self.assertFalse(prepare_mock.called)
+ self.assertFalse(failed_state_mock.called)
+
@mock.patch.object(agent_base_vendor.BaseAgentVendor,
'notify_conductor_resume_clean', autospec=True)
@mock.patch.object(agent_client.AgentClient, 'get_commands_status',
diff --git a/ironic/tests/unit/drivers/modules/test_iscsi_deploy.py b/ironic/tests/unit/drivers/modules/test_iscsi_deploy.py
index e6644bda8..9c8343bcb 100644
--- a/ironic/tests/unit/drivers/modules/test_iscsi_deploy.py
+++ b/ironic/tests/unit/drivers/modules/test_iscsi_deploy.py
@@ -61,7 +61,7 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
instance_info=INST_INFO_DICT,
driver_internal_info=DRV_INTERNAL_INFO_DICT
)
- info = iscsi_deploy.parse_instance_info(node)
+ info = deploy_utils.parse_instance_info(node)
self.assertIsNotNone(info.get('image_source'))
self.assertIsNotNone(info.get('root_gb'))
self.assertEqual(0, info.get('ephemeral_gb'))
@@ -76,7 +76,7 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
self.assertRaises(exception.MissingParameterValue,
- iscsi_deploy.parse_instance_info,
+ deploy_utils.parse_instance_info,
node)
def test_parse_instance_info_missing_root_gb(self):
@@ -89,7 +89,7 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
self.assertRaises(exception.MissingParameterValue,
- iscsi_deploy.parse_instance_info,
+ deploy_utils.parse_instance_info,
node)
def test_parse_instance_info_invalid_root_gb(self):
@@ -100,7 +100,7 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
self.assertRaises(exception.InvalidParameterValue,
- iscsi_deploy.parse_instance_info,
+ deploy_utils.parse_instance_info,
node)
def test_parse_instance_info_valid_ephemeral_gb(self):
@@ -113,10 +113,22 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
self.context, instance_info=info,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
- data = iscsi_deploy.parse_instance_info(node)
+ data = deploy_utils.parse_instance_info(node)
self.assertEqual(ephemeral_gb, data.get('ephemeral_gb'))
self.assertEqual(ephemeral_fmt, data.get('ephemeral_format'))
+ def test_parse_instance_info_unicode_swap_mb(self):
+ swap_mb = u'10'
+ swap_mb_int = 10
+ info = dict(INST_INFO_DICT)
+ info['swap_mb'] = swap_mb
+ node = obj_utils.create_test_node(
+ self.context, instance_info=info,
+ driver_internal_info=DRV_INTERNAL_INFO_DICT,
+ )
+ data = deploy_utils.parse_instance_info(node)
+ self.assertEqual(swap_mb_int, data.get('swap_mb'))
+
def test_parse_instance_info_invalid_ephemeral_gb(self):
info = dict(INST_INFO_DICT)
info['ephemeral_gb'] = 'foobar'
@@ -127,7 +139,7 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
self.assertRaises(exception.InvalidParameterValue,
- iscsi_deploy.parse_instance_info,
+ deploy_utils.parse_instance_info,
node)
def test_parse_instance_info_valid_ephemeral_missing_format(self):
@@ -141,7 +153,7 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
self.context, instance_info=info,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
- instance_info = iscsi_deploy.parse_instance_info(node)
+ instance_info = deploy_utils.parse_instance_info(node)
self.assertEqual(ephemeral_fmt, instance_info['ephemeral_format'])
def test_parse_instance_info_valid_preserve_ephemeral_true(self):
@@ -155,7 +167,7 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
instance_info=info,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
- data = iscsi_deploy.parse_instance_info(node)
+ data = deploy_utils.parse_instance_info(node)
self.assertTrue(data.get('preserve_ephemeral'))
def test_parse_instance_info_valid_preserve_ephemeral_false(self):
@@ -168,7 +180,7 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
instance_info=info,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
- data = iscsi_deploy.parse_instance_info(node)
+ data = deploy_utils.parse_instance_info(node)
self.assertFalse(data.get('preserve_ephemeral'))
def test_parse_instance_info_invalid_preserve_ephemeral(self):
@@ -179,7 +191,7 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
self.assertRaises(exception.InvalidParameterValue,
- iscsi_deploy.parse_instance_info,
+ deploy_utils.parse_instance_info,
node)
def test_parse_instance_info_invalid_ephemeral_disk(self):
@@ -197,7 +209,7 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
driver_internal_info=drv_internal_dict,
)
self.assertRaises(exception.InvalidParameterValue,
- iscsi_deploy.parse_instance_info,
+ deploy_utils.parse_instance_info,
node)
def test__check_disk_layout_unchanged_fails(self):
@@ -215,7 +227,7 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
driver_internal_info=drv_internal_dict,
)
self.assertRaises(exception.InvalidParameterValue,
- iscsi_deploy._check_disk_layout_unchanged,
+ deploy_utils._check_disk_layout_unchanged,
node, info)
def test__check_disk_layout_unchanged(self):
@@ -232,7 +244,7 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
self.context, instance_info=info,
driver_internal_info=drv_internal_dict,
)
- self.assertIsNone(iscsi_deploy._check_disk_layout_unchanged(node,
+ self.assertIsNone(deploy_utils._check_disk_layout_unchanged(node,
info))
def test__save_disk_layout(self):
@@ -259,7 +271,7 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
self.context, instance_info=info,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
- instance_info = iscsi_deploy.parse_instance_info(node)
+ instance_info = deploy_utils.parse_instance_info(node)
self.assertEqual('http://1.2.3.4/cd', instance_info['configdrive'])
def test_parse_instance_info_nonglance_image(self):
@@ -271,7 +283,7 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
self.context, instance_info=info,
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
- iscsi_deploy.parse_instance_info(node)
+ deploy_utils.parse_instance_info(node)
def test_parse_instance_info_nonglance_image_no_kernel(self):
info = INST_INFO_DICT.copy()
@@ -282,7 +294,7 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
driver_internal_info=DRV_INTERNAL_INFO_DICT,
)
self.assertRaises(exception.MissingParameterValue,
- iscsi_deploy.parse_instance_info, node)
+ deploy_utils.parse_instance_info, node)
def test_parse_instance_info_whole_disk_image(self):
driver_internal_info = dict(DRV_INTERNAL_INFO_DICT)
@@ -291,7 +303,7 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
self.context, instance_info=INST_INFO_DICT,
driver_internal_info=driver_internal_info,
)
- instance_info = iscsi_deploy.parse_instance_info(node)
+ instance_info = deploy_utils.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'))
@@ -303,7 +315,7 @@ class IscsiDeployValidateParametersTestCase(db_base.DbTestCase):
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)
+ deploy_utils.parse_instance_info, node)
class IscsiDeployPrivateMethodsTestCase(db_base.DbTestCase):
diff --git a/releasenotes/notes/agent_partition_image-48a03700f41a3980.yaml b/releasenotes/notes/agent_partition_image-48a03700f41a3980.yaml
new file mode 100644
index 000000000..c5089cf75
--- /dev/null
+++ b/releasenotes/notes/agent_partition_image-48a03700f41a3980.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - Adds support for partition images for agent
+ based drivers.