From 644a1c36b444fadbdda6ae9f0174ed6da385111a Mon Sep 17 00:00:00 2001 From: Faizan Barmawer Date: Mon, 16 Feb 2015 05:42:08 -0800 Subject: [PATCH] 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 Depends-on: I22bc29a39bf5c35f3eecb6d4e51cebd6aee0ce1 Depends-on: I37908470484744bb720f741d378106d1cb1227a3 Change-Id: Ifc8ba098f13b6fde712a584798fceb0321137bc9 --- ironic/drivers/modules/agent.py | 78 +++- ironic/drivers/modules/agent_base_vendor.py | 24 ++ ironic/drivers/modules/agent_config.template | 8 + ironic/drivers/modules/deploy_utils.py | 111 ++++++ ironic/drivers/modules/iscsi_deploy.py | 140 +------ ironic/tests/unit/common/test_pxe_utils.py | 4 +- .../unit/drivers/agent_pxe_config.template | 9 + .../tests/unit/drivers/modules/test_agent.py | 350 ++++++++++++++++-- .../drivers/modules/test_agent_base_vendor.py | 88 +++++ .../unit/drivers/modules/test_iscsi_deploy.py | 48 ++- ...gent_partition_image-48a03700f41a3980.yaml | 4 + 11 files changed, 676 insertions(+), 188 deletions(-) create mode 100644 releasenotes/notes/agent_partition_image-48a03700f41a3980.yaml diff --git a/ironic/drivers/modules/agent.py b/ironic/drivers/modules/agent.py index e8c04f3013..65aba00c51 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 7b6758ec11..bf10d1a35f 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 5c219cacb9..cf1a871586 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 88c69fc423..7b615a9b84 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 a7a464830f..0206b738d0 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 bb584c92d8..6f73caaa7d 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 7b26d58cf0..347d05c1b9 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 595322b2a8..147612e3ee 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( @@ -287,19 +403,6 @@ class TestAgentDeploy(db_base.DbTestCase): pxe_boot_validate_mock.assert_called_once_with( task.driver.boot, task) - @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( @@ -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,83 @@ 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() @@ -634,8 +850,96 @@ class TestAgentVendor(db_base.DbTestCase): 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) - bootdev_mock.assert_called_once_with(task, 'disk', persistent=True) + 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() + 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) + 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) + 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 63227c84eb..b4c1ded179 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 e6644bda89..9c8343bcb4 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 0000000000..c5089cf759 --- /dev/null +++ b/releasenotes/notes/agent_partition_image-48a03700f41a3980.yaml @@ -0,0 +1,4 @@ +--- +features: + - Adds support for partition images for agent + based drivers.