From da9ed8d0eb1c5d467b41f61185501c26ea2e4ce5 Mon Sep 17 00:00:00 2001 From: Ramakrishnan G Date: Fri, 27 Feb 2015 02:44:15 -0800 Subject: [PATCH] Add localboot support for uefi boot mode This commit adds localboot support for uefi boot mode. For uefi localboot, we switch partition table to gpt and create a efi system partition. The efi system partition is used later by bootloader (which is installed from ramdisk). Corresponding diskimage-builder change is here: Idf7ac5987e14e1d31311834196ca7283deec15c6 Implements: blueprint local-boot-support-with-partition-images Change-Id: I00ac31da325676ea4ea1ac4185f5ac3a52c5809a --- etc/ironic/ironic.conf.sample | 4 + ironic/drivers/modules/deploy_utils.py | 62 ++++-- .../modules/elilo_efi_pxe_config.template | 2 +- ironic/drivers/modules/ilo/deploy.py | 3 +- ironic/drivers/modules/iscsi_deploy.py | 26 ++- ironic/drivers/modules/pxe.py | 11 - ironic/drivers/modules/pxe_config.template | 2 +- ironic/tests/drivers/ilo/test_deploy.py | 6 + ironic/tests/drivers/pxe_config.template | 2 +- ironic/tests/drivers/test_deploy_utils.py | 197 +++++++++++++----- ironic/tests/drivers/test_iscsi_deploy.py | 20 +- ironic/tests/drivers/test_pxe.py | 19 +- ironic/tests/test_pxe_utils.py | 1 + 13 files changed, 251 insertions(+), 104 deletions(-) diff --git a/etc/ironic/ironic.conf.sample b/etc/ironic/ironic.conf.sample index 4af89adb20..76ff277b12 100644 --- a/etc/ironic/ironic.conf.sample +++ b/etc/ironic/ironic.conf.sample @@ -774,6 +774,10 @@ # Options defined in ironic.drivers.modules.deploy_utils # +# Size of EFI system partition in MiB when configuring UEFI +# systems for local boot. (integer value) +#efi_system_partition_size=200 + # Block size to use when writing to the nodes disk. (string # value) #dd_block_size=1M diff --git a/ironic/drivers/modules/deploy_utils.py b/ironic/drivers/modules/deploy_utils.py index 8318ae754b..4e2eee302e 100644 --- a/ironic/drivers/modules/deploy_utils.py +++ b/ironic/drivers/modules/deploy_utils.py @@ -49,6 +49,10 @@ from ironic.openstack.common import log as logging deploy_opts = [ + cfg.IntOpt('efi_system_partition_size', + default=200, + help='Size of EFI system partition in MiB when configuring ' + 'UEFI systems for local boot.'), cfg.StrOpt('dd_block_size', default='1M', help='Block size to use when writing to the nodes disk.'), @@ -209,7 +213,8 @@ def get_disk_identifier(dev): def make_partitions(dev, root_mb, swap_mb, ephemeral_mb, - configdrive_mb, commit=True, boot_option="netboot"): + configdrive_mb, commit=True, boot_option="netboot", + boot_mode="bios"): """Partition the disk device. Create partitions for root, swap, ephemeral and configdrive on a @@ -225,6 +230,7 @@ def make_partitions(dev, root_mb, swap_mb, ephemeral_mb, :param commit: True/False. Default for this setting is True. If False partitions will not be written to disk. :param boot_option: Can be "local" or "netboot". "netboot" by default. + :param boot_mode: Can be "bios" or "uefi". "bios" by default. :returns: A dictionary containing the partition type as Key and partition path as Value for the partitions created by this method. @@ -233,7 +239,18 @@ def make_partitions(dev, root_mb, swap_mb, ephemeral_mb, {'dev': dev}) part_template = dev + '-part%d' part_dict = {} - dp = disk_partitioner.DiskPartitioner(dev) + + # For uefi localboot, switch partition table to gpt and create the efi + # system partition as the first partition. + if boot_mode == "uefi" and boot_option == "local": + dp = disk_partitioner.DiskPartitioner(dev, disk_label="gpt") + part_num = dp.add_partition(CONF.deploy.efi_system_partition_size, + fs_type='fat32', + bootable=True) + part_dict['efi system partition'] = part_template % part_num + else: + dp = disk_partitioner.DiskPartitioner(dev) + if ephemeral_mb: LOG.debug("Add ephemeral partition (%(size)d MB) to device: %(dev)s", {'dev': dev, 'size': ephemeral_mb}) @@ -255,7 +272,8 @@ def make_partitions(dev, root_mb, swap_mb, ephemeral_mb, # partition until the end of the disk. LOG.debug("Add root partition (%(size)d MB) to device: %(dev)s", {'dev': dev, 'size': root_mb}) - part_num = dp.add_partition(root_mb, bootable=(boot_option == "local")) + part_num = dp.add_partition(root_mb, bootable=(boot_option == "local" and + boot_mode == "bios")) part_dict['root'] = part_template % part_num if commit: @@ -296,13 +314,11 @@ def populate_image(src, dst): images.convert_image(src, dst, 'raw', True) -def mkswap(dev, label='swap1'): - """Execute mkswap on a device.""" - utils.mkfs('swap', dev, label) - - -def mkfs_ephemeral(dev, ephemeral_format, label="ephemeral0"): - utils.mkfs(ephemeral_format, dev, label) +# TODO(rameshg87): Remove this one-line method and use utils.mkfs +# directly. +def mkfs(fs, dev, label=None): + """Execute mkfs on a device.""" + utils.mkfs(fs, dev, label) def block_uuid(dev): @@ -518,7 +534,8 @@ def _get_configdrive(configdrive, node_uuid): def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, image_path, node_uuid, preserve_ephemeral=False, - configdrive=None, boot_option="netboot"): + configdrive=None, boot_option="netboot", + boot_mode="bios"): """Create partitions and copy an image to the root partition. :param dev: Path for the device to work on. @@ -536,6 +553,7 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, :param configdrive: Optional. Base64 encoded Gzipped configdrive content or configdrive HTTP URL. :param boot_option: Can be "local" or "netboot". "netboot" by default. + :param boot_mode: Can be "bios" or "uefi". "bios" by default. :returns: the UUID of the root partition. """ # the only way for preserve_ephemeral to be set to true is if we are @@ -556,7 +574,8 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, part_dict = make_partitions(dev, root_mb, swap_mb, ephemeral_mb, configdrive_mb, commit=commit, - boot_option=boot_option) + boot_option=boot_option, + boot_mode=boot_mode) ephemeral_part = part_dict.get('ephemeral') swap_part = part_dict.get('swap') @@ -567,7 +586,8 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, raise exception.InstanceDeployFailure( _("Root device '%s' not found") % root_part) - for part in ('swap', 'ephemeral', 'configdrive'): + for part in ('swap', 'ephemeral', 'configdrive', + 'efi system partition'): part_device = part_dict.get(part) LOG.debug("Checking for %(part)s device (%(dev)s) on node " "%(node)s.", {'part': part, 'dev': part_device, @@ -577,6 +597,12 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, _("'%(partition)s' device '%(part_device)s' not found") % {'partition': part, 'part_device': part_device}) + # If it's a uefi localboot, then we have created the efi system + # partition. Create a fat filesystem on it. + if boot_mode == "uefi" and boot_option == "local": + efi_system_part = part_dict.get('efi system partition') + mkfs(dev=efi_system_part, fs='vfat', label='efi-part') + if configdrive_part: # Copy the configdrive content to the configdrive partition dd(configdrive_file, configdrive_part) @@ -590,10 +616,10 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, populate_image(image_path, root_part) if swap_part: - mkswap(swap_part) + mkfs(dev=swap_part, fs='swap', label='swap1') if ephemeral_part and not preserve_ephemeral: - mkfs_ephemeral(ephemeral_part, ephemeral_format) + mkfs(dev=ephemeral_part, fs=ephemeral_format, label="ephemeral0") try: root_uuid = block_uuid(root_part) @@ -607,7 +633,7 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, def deploy_partition_image(address, port, iqn, lun, image_path, root_mb, swap_mb, ephemeral_mb, ephemeral_format, node_uuid, preserve_ephemeral=False, configdrive=None, - boot_option="netboot"): + boot_option="netboot", boot_mode="bios"): """All-in-one function to deploy a partition image to a node. :param address: The iSCSI IP address. @@ -628,6 +654,7 @@ def deploy_partition_image(address, port, iqn, lun, image_path, :param configdrive: Optional. Base64 encoded Gzipped configdrive content or configdrive HTTP URL. :param boot_option: Can be "local" or "netboot". "netboot" by default. + :param boot_mode: Can be "bios" or "uefi". "bios" by default. :returns: the UUID of the root partition. """ with _iscsi_setup_and_handle_errors(address, port, iqn, @@ -640,7 +667,8 @@ def deploy_partition_image(address, port, iqn, lun, image_path, ephemeral_format, image_path, node_uuid, preserve_ephemeral=preserve_ephemeral, configdrive=configdrive, - boot_option=boot_option) + boot_option=boot_option, + boot_mode=boot_mode) return root_uuid diff --git a/ironic/drivers/modules/elilo_efi_pxe_config.template b/ironic/drivers/modules/elilo_efi_pxe_config.template index 71e6809821..88e1831d97 100644 --- a/ironic/drivers/modules/elilo_efi_pxe_config.template +++ b/ironic/drivers/modules/elilo_efi_pxe_config.template @@ -3,7 +3,7 @@ default=deploy image={{pxe_options.deployment_aki_path}} label=deploy initrd={{pxe_options.deployment_ari_path}} - append="selinux=0 disk={{ pxe_options.disk }} iscsi_target_iqn={{ pxe_options.iscsi_target_iqn }} deployment_id={{ pxe_options.deployment_id }} deployment_key={{ pxe_options.deployment_key }} ironic_api_url={{ pxe_options.ironic_api_url }} troubleshoot=0 text {{ pxe_options.pxe_append_params|default("", true) }} ip=%I:{{pxe_options.tftp_server}}:%G:%M:%H::on" {% if pxe_options.root_device %}root_device={{ pxe_options.root_device }}{% endif %} ipa-api-url={{ pxe_options['ipa-api-url'] }} ipa-driver-name={{ pxe_options['ipa-driver-name'] }} + append="selinux=0 disk={{ pxe_options.disk }} iscsi_target_iqn={{ pxe_options.iscsi_target_iqn }} deployment_id={{ pxe_options.deployment_id }} deployment_key={{ pxe_options.deployment_key }} ironic_api_url={{ pxe_options.ironic_api_url }} troubleshoot=0 text {{ pxe_options.pxe_append_params|default("", true) }} ip=%I:{{pxe_options.tftp_server}}:%G:%M:%H::on {% if pxe_options.root_device %}root_device={{ pxe_options.root_device }}{% endif %} ipa-api-url={{ pxe_options['ipa-api-url'] }} ipa-driver-name={{ pxe_options['ipa-driver-name'] }} boot_option={{ pxe_options.boot_option }} boot_mode={{ pxe_options['boot_mode'] }}" image={{pxe_options.aki_path}} diff --git a/ironic/drivers/modules/ilo/deploy.py b/ironic/drivers/modules/ilo/deploy.py index fd814209a1..f3d03212dd 100644 --- a/ironic/drivers/modules/ilo/deploy.py +++ b/ironic/drivers/modules/ilo/deploy.py @@ -177,7 +177,8 @@ def _clean_up_boot_iso_for_instance(node): :param node: an ironic node object. """ - if not node.instance_info['ilo_boot_iso'].startswith('swift'): + ilo_boot_iso = node.instance_info.get('ilo_boot_iso') + if not (ilo_boot_iso and ilo_boot_iso.startswith('swift')): return swift_api = swift.SwiftAPI() container = CONF.ilo.swift_ilo_container diff --git a/ironic/drivers/modules/iscsi_deploy.py b/ironic/drivers/modules/iscsi_deploy.py index b93357528c..0c080121f9 100644 --- a/ironic/drivers/modules/iscsi_deploy.py +++ b/ironic/drivers/modules/iscsi_deploy.py @@ -233,7 +233,8 @@ def get_deploy_info(node, **kwargs): if i_info['deploy_key'] != deploy_key: raise exception.InvalidParameterValue(_("Deploy key does not match")) - params = {'address': kwargs.get('address'), + params = { + 'address': kwargs.get('address'), 'port': kwargs.get('port', '3260'), 'iqn': kwargs.get('iqn'), 'lun': kwargs.get('lun', '1'), @@ -246,7 +247,8 @@ def get_deploy_info(node, **kwargs): 'swap_mb': int(i_info['swap_mb']), 'ephemeral_mb': 1024 * int(i_info['ephemeral_gb']), 'preserve_ephemeral': i_info['preserve_ephemeral'], - 'boot_option': get_boot_option(node)}) + 'boot_option': get_boot_option(node), + 'boot_mode': _get_boot_mode(node)}) missing = [key for key in params if params[key] is None] if missing: @@ -407,17 +409,30 @@ def parse_root_device_hints(node): def get_boot_option(node): - """Get the boot mode. + """Gets the boot option. :param node: A single Node. :raises: InvalidParameterValue if the capabilities string is not a - dict or is malformed. - :returns: A string representing the boot mode type. Defaults to 'netboot'. + dict or is malformed. + :returns: A string representing the boot option type. Defaults to + 'netboot'. """ capabilities = deploy_utils.parse_instance_info_capabilities(node) return capabilities.get('boot_option', 'netboot').lower() +def _get_boot_mode(node): + """Gets the boot mode. + + :param node: A single Node. + :returns: A string representing the boot mode type. Defaults to 'bios'. + """ + boot_mode = driver_utils.get_node_capability(node, 'boot_mode') + if boot_mode: + return boot_mode.lower() + return "bios" + + def build_deploy_ramdisk_options(node): """Build the ramdisk config options for a node @@ -446,6 +461,7 @@ def build_deploy_ramdisk_options(node): 'ironic_api_url': ironic_api, 'disk': CONF.pxe.disk_devices, 'boot_option': get_boot_option(node), + 'boot_mode': _get_boot_mode(node), } root_device = parse_root_device_hints(node) diff --git a/ironic/drivers/modules/pxe.py b/ironic/drivers/modules/pxe.py index 4d5d836a4d..99389fa29c 100644 --- a/ironic/drivers/modules/pxe.py +++ b/ironic/drivers/modules/pxe.py @@ -328,17 +328,6 @@ class PXEDeploy(base.DeployInterface): driver_utils.validate_boot_option_capability(node) boot_mode = driver_utils.get_node_capability(node, 'boot_mode') - boot_option = driver_utils.get_node_capability(node, 'boot_option') - - # NOTE(lucasagomes): We don't support UEFI + localboot because - # we do not support creating an EFI boot partition, including the - # EFI modules and managing the bootloader variables via efibootmgr. - if boot_mode == 'uefi' and boot_option == 'local': - error_msg = (_("Local boot is requested, but can't be " - "used with node %s because it's configured " - "to use UEFI boot") % node.uuid) - LOG.error(error_msg) - raise exception.InvalidParameterValue(error_msg) if CONF.pxe.ipxe_enabled: if not CONF.pxe.http_url or not CONF.pxe.http_root: diff --git a/ironic/drivers/modules/pxe_config.template b/ironic/drivers/modules/pxe_config.template index d231ca80b6..334a6ac81d 100644 --- a/ironic/drivers/modules/pxe_config.template +++ b/ironic/drivers/modules/pxe_config.template @@ -2,7 +2,7 @@ default deploy label deploy kernel {{ pxe_options.deployment_aki_path }} -append initrd={{ pxe_options.deployment_ari_path }} selinux=0 disk={{ pxe_options.disk }} iscsi_target_iqn={{ pxe_options.iscsi_target_iqn }} deployment_id={{ pxe_options.deployment_id }} deployment_key={{ pxe_options.deployment_key }} ironic_api_url={{ pxe_options.ironic_api_url }} troubleshoot=0 text {{ pxe_options.pxe_append_params|default("", true) }} boot_option={{ pxe_options.boot_option }} {% if pxe_options.root_device %}root_device={{ pxe_options.root_device }}{% endif %} ipa-api-url={{ pxe_options['ipa-api-url'] }} ipa-driver-name={{ pxe_options['ipa-driver-name'] }} +append initrd={{ pxe_options.deployment_ari_path }} selinux=0 disk={{ pxe_options.disk }} iscsi_target_iqn={{ pxe_options.iscsi_target_iqn }} deployment_id={{ pxe_options.deployment_id }} deployment_key={{ pxe_options.deployment_key }} ironic_api_url={{ pxe_options.ironic_api_url }} troubleshoot=0 text {{ pxe_options.pxe_append_params|default("", true) }} boot_option={{ pxe_options.boot_option }} {% if pxe_options.root_device %}root_device={{ pxe_options.root_device }}{% endif %} ipa-api-url={{ pxe_options['ipa-api-url'] }} ipa-driver-name={{ pxe_options['ipa-driver-name'] }} boot_mode={{ pxe_options['boot_mode'] }} ipappend 3 diff --git a/ironic/tests/drivers/ilo/test_deploy.py b/ironic/tests/drivers/ilo/test_deploy.py index 67d3b471db..e3dbc8bebd 100644 --- a/ironic/tests/drivers/ilo/test_deploy.py +++ b/ironic/tests/drivers/ilo/test_deploy.py @@ -199,6 +199,12 @@ class IloDeployPrivateMethodsTestCase(db_base.DbTestCase): swift_obj_mock.delete_object.assert_called_once_with('ilo-cont', 'boot-object') + @mock.patch.object(ilo_deploy, '_get_boot_iso_object_name') + def test__clean_up_boot_iso_for_instance_no_boot_iso( + self, boot_object_name_mock): + ilo_deploy._clean_up_boot_iso_for_instance(self.node) + self.assertFalse(boot_object_name_mock.called) + @mock.patch.object(deploy_utils, 'check_for_missing_params') def test__parse_driver_info(self, check_params_mock): self.node.driver_info['ilo_deploy_iso'] = 'deploy-iso-uuid' diff --git a/ironic/tests/drivers/pxe_config.template b/ironic/tests/drivers/pxe_config.template index c6a28313a1..d77f17e6c3 100644 --- a/ironic/tests/drivers/pxe_config.template +++ b/ironic/tests/drivers/pxe_config.template @@ -2,7 +2,7 @@ default deploy label deploy kernel /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_kernel -append initrd=/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_ramdisk selinux=0 disk=cciss/c0d0,sda,hda,vda iscsi_target_iqn=iqn-1be26c0b-03f2-4d2e-ae87-c02d7f33c123 deployment_id=1be26c0b-03f2-4d2e-ae87-c02d7f33c123 deployment_key=0123456789ABCDEFGHIJKLMNOPQRSTUV ironic_api_url=http://192.168.122.184:6385 troubleshoot=0 text test_param boot_option=netboot root_device=vendor=fake,size=123 ipa-api-url=http://192.168.122.184:6385 ipa-driver-name=pxe_ssh +append initrd=/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_ramdisk selinux=0 disk=cciss/c0d0,sda,hda,vda iscsi_target_iqn=iqn-1be26c0b-03f2-4d2e-ae87-c02d7f33c123 deployment_id=1be26c0b-03f2-4d2e-ae87-c02d7f33c123 deployment_key=0123456789ABCDEFGHIJKLMNOPQRSTUV ironic_api_url=http://192.168.122.184:6385 troubleshoot=0 text test_param boot_option=netboot root_device=vendor=fake,size=123 ipa-api-url=http://192.168.122.184:6385 ipa-driver-name=pxe_ssh boot_mode=bios ipappend 3 diff --git a/ironic/tests/drivers/test_deploy_utils.py b/ironic/tests/drivers/test_deploy_utils.py index 6789b96e6d..493ee68b5f 100644 --- a/ironic/tests/drivers/test_deploy_utils.py +++ b/ironic/tests/drivers/test_deploy_utils.py @@ -233,7 +233,7 @@ class PhysicalWorkTestCase(tests_base.TestCase): parent_mock.attach_mock(mocker, name) return parent_mock - def _test_deploy_partition_image(self, boot_option=None): + def _test_deploy_partition_image(self, boot_option=None, boot_mode=None): """Check loosely all functions are called with right args.""" address = '127.0.0.1' port = 3306 @@ -254,7 +254,7 @@ class PhysicalWorkTestCase(tests_base.TestCase): name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi', 'logout_iscsi', 'delete_iscsi', 'make_partitions', - 'is_block_device', 'populate_image', 'mkswap', + 'is_block_device', 'populate_image', 'mkfs', 'block_uuid', 'notify', 'destroy_disk_metadata'] parent_mock = self._mock_calls(name_list) parent_mock.get_dev.return_value = dev @@ -263,41 +263,46 @@ class PhysicalWorkTestCase(tests_base.TestCase): parent_mock.block_uuid.return_value = root_uuid parent_mock.make_partitions.return_value = {'root': root_part, 'swap': swap_part} + + make_partitions_expected_args = [dev, root_mb, swap_mb, ephemeral_mb, + configdrive_mb] + make_partitions_expected_kwargs = {'commit': True} + deploy_kwargs = {} + + if boot_option: + make_partitions_expected_kwargs['boot_option'] = boot_option + deploy_kwargs['boot_option'] = boot_option + else: + make_partitions_expected_kwargs['boot_option'] = 'netboot' + + if boot_mode: + make_partitions_expected_kwargs['boot_mode'] = boot_mode + deploy_kwargs['boot_mode'] = boot_mode + else: + make_partitions_expected_kwargs['boot_mode'] = 'bios' + + # If no boot_option, then it should default to netboot. calls_expected = [mock.call.get_dev(address, port, iqn, lun), mock.call.discovery(address, port), mock.call.login_iscsi(address, port, iqn), mock.call.is_block_device(dev), mock.call.get_image_mb(image_path), - mock.call.destroy_disk_metadata(dev, node_uuid)] + mock.call.destroy_disk_metadata(dev, node_uuid), + mock.call.make_partitions( + *make_partitions_expected_args, + **make_partitions_expected_kwargs), + mock.call.is_block_device(root_part), + mock.call.is_block_device(swap_part), + mock.call.populate_image(image_path, root_part), + mock.call.mkfs(dev=swap_part, fs='swap', + label='swap1'), + mock.call.block_uuid(root_part), + mock.call.logout_iscsi(address, port, iqn), + mock.call.delete_iscsi(address, port, iqn)] - if boot_option: - calls_expected.append(mock.call.make_partitions( - dev, root_mb, swap_mb, ephemeral_mb, configdrive_mb, - commit=True, boot_option=boot_option)) - else: - # If no boot_option, then it should default to netboot. - calls_expected.append(mock.call.make_partitions( - dev, root_mb, swap_mb, ephemeral_mb, configdrive_mb, - commit=True, boot_option="netboot")) - - calls_expected.extend([mock.call.is_block_device(root_part), - mock.call.is_block_device(swap_part), - mock.call.populate_image(image_path, root_part), - mock.call.mkswap(swap_part), - mock.call.block_uuid(root_part), - mock.call.logout_iscsi(address, port, iqn), - mock.call.delete_iscsi(address, port, iqn)]) - - kwargs = {} - if boot_option: - kwargs = {'boot_option': boot_option} - - returned_root_uuid = utils.deploy_partition_image(address, port, iqn, - lun, image_path, - root_mb, swap_mb, - ephemeral_mb, - ephemeral_format, - node_uuid, **kwargs) + returned_root_uuid = utils.deploy_partition_image( + address, port, iqn, lun, image_path, root_mb, swap_mb, + ephemeral_mb, ephemeral_format, node_uuid, **deploy_kwargs) self.assertEqual(calls_expected, parent_mock.mock_calls) self.assertEqual(root_uuid, returned_root_uuid) @@ -311,6 +316,87 @@ class PhysicalWorkTestCase(tests_base.TestCase): def test_deploy_partition_image_localboot(self): self._test_deploy_partition_image(boot_option="local") + def test_deploy_partition_image_wo_boot_option_and_wo_boot_mode(self): + self._test_deploy_partition_image() + + def test_deploy_partition_image_netboot_bios(self): + self._test_deploy_partition_image(boot_option="netboot", + boot_mode="bios") + + def test_deploy_partition_image_localboot_bios(self): + self._test_deploy_partition_image(boot_option="local", + boot_mode="bios") + + def test_deploy_partition_image_netboot_uefi(self): + self._test_deploy_partition_image(boot_option="netboot", + boot_mode="uefi") + + def test_deploy_partition_image_localboot_uefi(self): + """Check loosely all functions are called with right args.""" + address = '127.0.0.1' + port = 3306 + iqn = 'iqn.xyz' + lun = 1 + image_path = '/tmp/xyz/image' + root_mb = 128 + swap_mb = 64 + ephemeral_mb = 0 + ephemeral_format = None + configdrive_mb = 0 + node_uuid = "12345678-1234-1234-1234-1234567890abcxyz" + + dev = '/dev/fake' + swap_part = '/dev/fake-part2' + root_part = '/dev/fake-part3' + efi_system_part = '/dev/fake-part1' + root_uuid = '12345678-1234-1234-12345678-12345678abcdef' + + name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi', + 'logout_iscsi', 'delete_iscsi', 'make_partitions', + 'is_block_device', 'populate_image', 'mkfs', + 'block_uuid', 'notify', 'destroy_disk_metadata'] + parent_mock = self._mock_calls(name_list) + parent_mock.get_dev.return_value = dev + parent_mock.get_image_mb.return_value = 1 + parent_mock.is_block_device.return_value = True + parent_mock.block_uuid.return_value = root_uuid + parent_mock.make_partitions.return_value = { + 'root': root_part, 'swap': swap_part, + 'efi system partition': efi_system_part} + + # If no boot_option, then it should default to netboot. + calls_expected = [mock.call.get_dev(address, port, iqn, lun), + mock.call.discovery(address, port), + mock.call.login_iscsi(address, port, iqn), + mock.call.is_block_device(dev), + mock.call.get_image_mb(image_path), + mock.call.destroy_disk_metadata(dev, node_uuid), + mock.call.make_partitions(dev, root_mb, swap_mb, + ephemeral_mb, + configdrive_mb, + commit=True, + boot_option="local", + boot_mode="uefi"), + mock.call.is_block_device(root_part), + mock.call.is_block_device(swap_part), + mock.call.is_block_device(efi_system_part), + mock.call.mkfs(dev=efi_system_part, fs='vfat', + label='efi-part'), + mock.call.populate_image(image_path, root_part), + mock.call.mkfs(dev=swap_part, fs='swap', + label='swap1'), + mock.call.block_uuid(root_part), + mock.call.logout_iscsi(address, port, iqn), + mock.call.delete_iscsi(address, port, iqn)] + + returned_root_uuid = utils.deploy_partition_image( + address, port, iqn, lun, image_path, root_mb, swap_mb, + ephemeral_mb, ephemeral_format, node_uuid, boot_option="local", + boot_mode="uefi") + + self.assertEqual(calls_expected, parent_mock.mock_calls) + self.assertEqual(root_uuid, returned_root_uuid) + def test_deploy_partition_image_without_swap(self): """Check loosely all functions are called with right args.""" address = '127.0.0.1' @@ -349,7 +435,8 @@ class PhysicalWorkTestCase(tests_base.TestCase): ephemeral_mb, configdrive_mb, commit=True, - boot_option="netboot"), + boot_option="netboot", + boot_mode="bios"), mock.call.is_block_device(root_part), mock.call.populate_image(image_path, root_part), mock.call.block_uuid(root_part), @@ -388,9 +475,8 @@ class PhysicalWorkTestCase(tests_base.TestCase): name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi', 'logout_iscsi', 'delete_iscsi', 'make_partitions', - 'is_block_device', 'populate_image', 'mkswap', - 'block_uuid', 'notify', 'mkfs_ephemeral', - 'destroy_disk_metadata'] + 'is_block_device', 'populate_image', 'mkfs', + 'block_uuid', 'notify', 'destroy_disk_metadata'] parent_mock = self._mock_calls(name_list) parent_mock.get_dev.return_value = dev parent_mock.get_image_mb.return_value = 1 @@ -409,14 +495,17 @@ class PhysicalWorkTestCase(tests_base.TestCase): ephemeral_mb, configdrive_mb, commit=True, - boot_option="netboot"), + boot_option="netboot", + boot_mode="bios"), mock.call.is_block_device(root_part), mock.call.is_block_device(swap_part), mock.call.is_block_device(ephemeral_part), mock.call.populate_image(image_path, root_part), - mock.call.mkswap(swap_part), - mock.call.mkfs_ephemeral(ephemeral_part, - ephemeral_format), + mock.call.mkfs(dev=swap_part, fs='swap', + label='swap1'), + mock.call.mkfs(dev=ephemeral_part, + fs=ephemeral_format, + label='ephemeral0'), mock.call.block_uuid(root_part), mock.call.logout_iscsi(address, port, iqn), mock.call.delete_iscsi(address, port, iqn)] @@ -453,9 +542,8 @@ class PhysicalWorkTestCase(tests_base.TestCase): name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi', 'logout_iscsi', 'delete_iscsi', 'make_partitions', - 'is_block_device', 'populate_image', 'mkswap', - 'block_uuid', 'notify', 'mkfs_ephemeral', - 'get_dev_block_size'] + 'is_block_device', 'populate_image', 'mkfs', + 'block_uuid', 'notify', 'get_dev_block_size'] parent_mock = self._mock_calls(name_list) parent_mock.get_dev.return_value = dev parent_mock.get_image_mb.return_value = 1 @@ -474,12 +562,14 @@ class PhysicalWorkTestCase(tests_base.TestCase): ephemeral_mb, configdrive_mb, commit=False, - boot_option="netboot"), + boot_option="netboot", + boot_mode="bios"), mock.call.is_block_device(root_part), mock.call.is_block_device(swap_part), mock.call.is_block_device(ephemeral_part), mock.call.populate_image(image_path, root_part), - mock.call.mkswap(swap_part), + mock.call.mkfs(dev=swap_part, fs='swap', + label='swap1'), mock.call.block_uuid(root_part), mock.call.logout_iscsi(address, port, iqn), mock.call.delete_iscsi(address, port, iqn)] @@ -493,7 +583,6 @@ class PhysicalWorkTestCase(tests_base.TestCase): preserve_ephemeral=True, boot_option="netboot") self.assertEqual(calls_expected, parent_mock.mock_calls) - self.assertFalse(parent_mock.mkfs_ephemeral.called) self.assertFalse(parent_mock.get_dev_block_size.called) self.assertEqual(root_uuid, returned_root_uuid) @@ -544,7 +633,8 @@ class PhysicalWorkTestCase(tests_base.TestCase): ephemeral_mb, configdrive_mb, commit=True, - boot_option="netboot"), + boot_option="netboot", + boot_mode="bios"), mock.call.is_block_device(root_part), mock.call.is_block_device(configdrive_part), mock.call.dd(mock.ANY, configdrive_part), @@ -726,7 +816,8 @@ class PhysicalWorkTestCase(tests_base.TestCase): ephemeral_format, image_path, node_uuid, configdrive=None, preserve_ephemeral=False, - boot_option="netboot"), + boot_option="netboot", + boot_mode="bios"), mock.call.logout_iscsi(address, port, iqn), mock.call.delete_iscsi(address, port, iqn)] @@ -902,7 +993,8 @@ class WorkOnDiskTestCase(tests_base.TestCase): self.mock_mp.assert_called_once_with(self.dev, self.root_mb, self.swap_mb, self.ephemeral_mb, self.configdrive_mb, commit=True, - boot_option="netboot") + boot_option="netboot", + boot_mode="bios") def test_no_swap_partition(self): self.mock_ibd.side_effect = [True, False] @@ -916,7 +1008,8 @@ class WorkOnDiskTestCase(tests_base.TestCase): self.mock_mp.assert_called_once_with(self.dev, self.root_mb, self.swap_mb, self.ephemeral_mb, self.configdrive_mb, commit=True, - boot_option="netboot") + boot_option="netboot", + boot_mode="bios") def test_no_ephemeral_partition(self): ephemeral_part = '/dev/fake-part1' @@ -940,7 +1033,8 @@ class WorkOnDiskTestCase(tests_base.TestCase): self.mock_mp.assert_called_once_with(self.dev, self.root_mb, self.swap_mb, ephemeral_mb, self.configdrive_mb, commit=True, - boot_option="netboot") + boot_option="netboot", + boot_mode="bios") @mock.patch.object(common_utils, 'unlink_without_raise') @mock.patch.object(utils, '_get_configdrive') @@ -970,7 +1064,8 @@ class WorkOnDiskTestCase(tests_base.TestCase): self.mock_mp.assert_called_once_with(self.dev, self.root_mb, self.swap_mb, self.ephemeral_mb, configdrive_mb, commit=True, - boot_option="netboot") + boot_option="netboot", + boot_mode="bios") mock_unlink.assert_called_once_with('fake-path') diff --git a/ironic/tests/drivers/test_iscsi_deploy.py b/ironic/tests/drivers/test_iscsi_deploy.py index 379904fdfc..b499eaa2dd 100644 --- a/ironic/tests/drivers/test_iscsi_deploy.py +++ b/ironic/tests/drivers/test_iscsi_deploy.py @@ -412,7 +412,8 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase): def _test_build_deploy_ramdisk_options(self, mock_alnum, api_url, expected_root_device=None, - expected_boot_option='netboot'): + expected_boot_option='netboot', + expected_boot_mode='bios'): fake_key = '0123456789ABCDEFGHIJKLMNOPQRSTUV' fake_disk = 'fake-disk' @@ -420,12 +421,15 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase): mock_alnum.return_value = fake_key - expected_opts = {'iscsi_target_iqn': 'iqn-%s' % self.node.uuid, + expected_opts = { + 'iscsi_target_iqn': 'iqn-%s' % self.node.uuid, 'deployment_id': self.node.uuid, 'deployment_key': fake_key, 'disk': fake_disk, 'ironic_api_url': api_url, - 'boot_option': expected_boot_option} + 'boot_option': expected_boot_option, + 'boot_mode': expected_boot_mode, + } if expected_root_device: expected_opts['root_device'] = expected_root_device @@ -482,6 +486,16 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase): self._test_build_deploy_ramdisk_options(mock_alnum, fake_api_url, expected_boot_option=expected) + @mock.patch.object(keystone, 'get_service_url') + @mock.patch.object(utils, 'random_alnum') + def test_build_deploy_ramdisk_options_boot_mode(self, mock_alnum, + mock_get_url): + self.node.properties['capabilities'] = 'boot_mode:uefi' + fake_api_url = 'http://127.0.0.1:6385' + self.config(api_url=fake_api_url, group='conductor') + self._test_build_deploy_ramdisk_options(mock_alnum, fake_api_url, + expected_boot_mode='uefi') + def test_parse_root_device_hints(self): self.node.properties['root_device'] = {'wwn': 123456} expected = 'wwn=123456' diff --git a/ironic/tests/drivers/test_pxe.py b/ironic/tests/drivers/test_pxe.py index ebc559ae60..00084166d8 100644 --- a/ironic/tests/drivers/test_pxe.py +++ b/ironic/tests/drivers/test_pxe.py @@ -193,12 +193,15 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase): self.config(api_url='http://192.168.122.184:6385', group='conductor') self.config(disk_devices='sda', group='pxe') - fake_deploy_opts = {'iscsi_target_iqn': 'fake-iqn', + fake_deploy_opts = { + 'iscsi_target_iqn': 'fake-iqn', 'deployment_id': 'fake-deploy-id', 'deployment_key': 'fake-deploy-key', 'disk': 'fake-disk', 'ironic_api_url': 'fake-api-url', - 'boot_option': 'netboot'} + 'boot_option': 'netboot', + 'boot_mode': 'bios', + } deploy_opts_mock.return_value = fake_deploy_opts @@ -237,6 +240,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase): 'boot_option': 'netboot', 'ipa-api-url': CONF.conductor.api_url, 'ipa-driver-name': self.node.driver, + 'boot_mode': 'bios', } expected_options.update(fake_deploy_opts) @@ -475,17 +479,6 @@ class PXEDriverTestCase(db_base.DbTestCase): self.assertRaises(exception.InvalidParameterValue, task.driver.deploy.validate, task) - @mock.patch.object(base_image_service.BaseImageService, '_show') - def test_validate_fail_invalid_uefi_and_localboot(self, mock_glance): - properties = {'capabilities': 'boot_mode:uefi,boot_option:local'} - mock_glance.return_value = {'properties': {'kernel_id': 'fake-kernel', - 'ramdisk_id': 'fake-initr'}} - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - task.node.properties = properties - self.assertRaises(exception.InvalidParameterValue, - task.driver.deploy.validate, task) - def test_validate_fail_no_port(self): new_node = obj_utils.create_test_node( self.context, diff --git a/ironic/tests/test_pxe_utils.py b/ironic/tests/test_pxe_utils.py index 78e38ded31..78ec957aec 100644 --- a/ironic/tests/test_pxe_utils.py +++ b/ironic/tests/test_pxe_utils.py @@ -53,6 +53,7 @@ class TestPXEUtils(db_base.DbTestCase): 'boot_option': 'netboot', 'ipa-api-url': 'http://192.168.122.184:6385', 'ipa-driver-name': 'pxe_ssh', + 'boot_mode': 'bios', } self.agent_pxe_options = { 'deployment_ari_path': u'/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7'