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
This commit is contained in:
Ramakrishnan G 2015-02-27 02:44:15 -08:00
parent dc08463ec1
commit da9ed8d0eb
13 changed files with 251 additions and 104 deletions

View File

@ -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

View File

@ -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 = {}
# 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

View File

@ -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}}

View File

@ -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

View File

@ -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'.
: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)

View File

@ -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:

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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)]
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.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.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)])
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')

View File

@ -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'

View File

@ -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,

View File

@ -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'