Software RAID: Add UEFI support
The proposed changes concern two steps: First, when creating the RAID configuration, have a GPT partition table type (this is not necessary, but more natural with UEFI). Also, leave some space, either for the EFI partitions or the BIOS boot partitions, outside the Software RAID. Secondly, when installing the bootloader, make sure the correct boot partitions are created or relocated. Change-Id: Icf0a76b0de89e7a8494363ec91b2f1afda4faa3b Story: #2006379 Task: #37635
This commit is contained in:
parent
d71a8375fa
commit
9343348106
|
@ -20,6 +20,7 @@ import shutil
|
|||
import stat
|
||||
import tempfile
|
||||
|
||||
from ironic_lib import utils as ilib_utils
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_log import log
|
||||
|
||||
|
@ -86,6 +87,7 @@ def _get_partition(device, uuid):
|
|||
# NOTE(TheJulia): We may want to consider moving towards using
|
||||
# findfs in the future, if we're comfortable with the execution
|
||||
# and interaction. There is value in either way though.
|
||||
# NOTE(rg): alternative: blkid -l -t UUID=/PARTUUID=
|
||||
try:
|
||||
findfs, stderr = utils.execute('findfs', 'UUID=%s' % uuid)
|
||||
return findfs.strip()
|
||||
|
@ -347,14 +349,127 @@ def _manage_uefi(device, efi_system_part_uuid=None):
|
|||
shutil.rmtree(local_path)
|
||||
|
||||
|
||||
# TODO(rg): handle PreP boot parts relocation as well
|
||||
def _prepare_boot_partitions_for_softraid(device, holders, efi_part,
|
||||
target_boot_mode):
|
||||
"""Prepare boot partitions when relevant.
|
||||
|
||||
Create either efi partitions or bios boot partitions for softraid,
|
||||
according to both target boot mode and disk holders partition table types.
|
||||
|
||||
:param device: the softraid device path
|
||||
:param holders: the softraid drive members
|
||||
:param efi_part: when relevant the efi partition coming from the image
|
||||
deployed on softraid device, can be/is often None
|
||||
:param target_boot_mode: target boot mode can be bios/uefi/None
|
||||
or anything else for unspecified
|
||||
|
||||
:returns: the efi partition paths on softraid disk holders when target
|
||||
boot mode is uefi, empty list otherwise.
|
||||
"""
|
||||
efi_partitions = []
|
||||
|
||||
# Actually any fat partition could be a candidate. Let's assume the
|
||||
# partition also has the esp flag
|
||||
if target_boot_mode == 'uefi':
|
||||
if not efi_part:
|
||||
|
||||
LOG.debug("No explicit EFI partition provided. Scanning for any "
|
||||
"EFI partition located on software RAID device %s to "
|
||||
"be relocated",
|
||||
device)
|
||||
|
||||
# NOTE: for whole disk images, no efi part uuid will be provided.
|
||||
# Let's try to scan for esp on the root softraid device. If not
|
||||
# found, it's fine in most cases to just create an empty esp and
|
||||
# let grub handle the magic.
|
||||
efi_part = utils.get_efi_part_on_device(device)
|
||||
if efi_part:
|
||||
efi_part = '{}p{}'.format(device, efi_part)
|
||||
|
||||
# We know that we kept this space when configuring raid,see
|
||||
# hardware.GenericHardwareManager.create_configuration.
|
||||
# We could also directly get the EFI partition size.
|
||||
partsize_mib = 128
|
||||
partlabel_prefix = 'uefi-holder-'
|
||||
for number, holder in enumerate(holders):
|
||||
# NOTE: see utils.get_partition_table_type_from_specs
|
||||
# for uefi we know that we have setup a gpt partition table,
|
||||
# sgdisk can be used to edit table, more user friendly
|
||||
# for alignment and relative offsets
|
||||
partlabel = '{}{}'.format(partlabel_prefix, number)
|
||||
out, _u = utils.execute('sgdisk', '-F', holder)
|
||||
start_sector = '{}s'.format(out.splitlines()[-1].strip())
|
||||
out, _u = utils.execute(
|
||||
'sgdisk', '-n', '0:{}:+{}MiB'.format(start_sector,
|
||||
partsize_mib),
|
||||
'-t', '0:ef00', '-c', '0:{}'.format(partlabel), holder)
|
||||
|
||||
# Refresh part table
|
||||
utils.execute("partprobe")
|
||||
utils.execute("blkid")
|
||||
|
||||
target_part, _u = utils.execute(
|
||||
"blkid", "-l", "-t", "PARTLABEL={}".format(partlabel), holder)
|
||||
|
||||
target_part = target_part.splitlines()[-1].split(':', 1)[0]
|
||||
|
||||
LOG.debug("Efi partition %s created on disk holder %s",
|
||||
target_part, holder)
|
||||
|
||||
if efi_part:
|
||||
LOG.debug("Relocating efi %s to holder part %s", efi_part,
|
||||
target_part)
|
||||
# Blockdev copy
|
||||
utils.execute("cp", efi_part, target_part)
|
||||
else:
|
||||
# Creating a label is just to make life easier
|
||||
if number == 0:
|
||||
fslabel = 'efi-part'
|
||||
else:
|
||||
# bak, label is limited to 11 chars
|
||||
fslabel = 'efi-part-b'
|
||||
ilib_utils.mkfs(fs='vfat', path=target_part, label=fslabel)
|
||||
efi_partitions.append(target_part)
|
||||
# TBD: Would not hurt to destroy source efi part when defined,
|
||||
# for clarity.
|
||||
|
||||
elif target_boot_mode == 'bios':
|
||||
partlabel_prefix = 'bios-boot-part-'
|
||||
for number, holder in enumerate(holders):
|
||||
label = utils.scan_partition_table_type(holder)
|
||||
if label == 'gpt':
|
||||
LOG.debug("Creating bios boot partition on disk holder %s",
|
||||
holder)
|
||||
out, _u = utils.execute('sgdisk', '-F', holder)
|
||||
start_sector = '{}s'.format(out.splitlines()[-1].strip())
|
||||
partlabel = '{}{}'.format(partlabel_prefix, number)
|
||||
out, _u = utils.execute(
|
||||
'sgdisk', '-n', '0:{}:+2MiB'.format(start_sector),
|
||||
'-t', '0:ef02', '-c', '0:{}'.format(partlabel), holder)
|
||||
|
||||
# Q: MBR case, could we dd the boot code from the softraid
|
||||
# (446 first bytes) if we detect a bootloader with
|
||||
# _is_bootloader_loaded?
|
||||
# A: This won't work. Because it includes the address on the
|
||||
# disk, as in virtual disk, where to load the data from.
|
||||
# Since there is a structural difference, this means it will
|
||||
# fail.
|
||||
|
||||
# Just an empty list if not uefi boot mode, nvm, not used anyway
|
||||
return efi_partitions
|
||||
|
||||
|
||||
def _install_grub2(device, root_uuid, efi_system_part_uuid=None,
|
||||
prep_boot_part_uuid=None):
|
||||
prep_boot_part_uuid=None, target_boot_mode='bios'):
|
||||
"""Install GRUB2 bootloader on a given device."""
|
||||
LOG.debug("Installing GRUB2 bootloader on device %s", device)
|
||||
|
||||
efi_partition = None
|
||||
efi_partitions = None
|
||||
efi_part = None
|
||||
efi_partition_mount_point = None
|
||||
efi_mounted = False
|
||||
holders = None
|
||||
|
||||
# NOTE(TheJulia): Seems we need to get this before ever possibly
|
||||
# restart the device in the case of multi-device RAID as pyudev
|
||||
|
@ -379,12 +494,21 @@ def _install_grub2(device, root_uuid, efi_system_part_uuid=None,
|
|||
LOG.info("Skipping installation of bootloader on device %s "
|
||||
"as it is already marked bootable.", device)
|
||||
return
|
||||
|
||||
try:
|
||||
# Mount the partition and binds
|
||||
path = tempfile.mkdtemp()
|
||||
|
||||
if efi_system_part_uuid:
|
||||
efi_partition = _get_partition(device, uuid=efi_system_part_uuid)
|
||||
efi_part = _get_partition(device, uuid=efi_system_part_uuid)
|
||||
efi_partitions = [efi_part]
|
||||
|
||||
if hardware.is_md_device(device):
|
||||
holders = hardware.get_holder_disks(device)
|
||||
efi_partitions = _prepare_boot_partitions_for_softraid(
|
||||
device, holders, efi_part, target_boot_mode
|
||||
)
|
||||
|
||||
if efi_partitions:
|
||||
efi_partition_mount_point = os.path.join(path, "boot/efi")
|
||||
|
||||
# For power we want to install grub directly onto the PreP partition
|
||||
|
@ -394,7 +518,7 @@ def _install_grub2(device, root_uuid, efi_system_part_uuid=None,
|
|||
# If the root device is an md device (or partition),
|
||||
# identify the underlying holder disks to install grub.
|
||||
if hardware.is_md_device(device):
|
||||
disks = hardware.get_holder_disks(device)
|
||||
disks = holders
|
||||
else:
|
||||
disks = [device]
|
||||
|
||||
|
@ -404,12 +528,6 @@ def _install_grub2(device, root_uuid, efi_system_part_uuid=None,
|
|||
|
||||
utils.execute('mount', '-t', 'sysfs', 'none', path + '/sys')
|
||||
|
||||
if efi_partition:
|
||||
if not os.path.exists(efi_partition_mount_point):
|
||||
os.makedirs(efi_partition_mount_point)
|
||||
utils.execute('mount', efi_partition, efi_partition_mount_point)
|
||||
efi_mounted = True
|
||||
|
||||
binary_name = "grub"
|
||||
if os.path.exists(os.path.join(path, 'usr/sbin/grub2-install')):
|
||||
binary_name = "grub2"
|
||||
|
@ -419,34 +537,77 @@ def _install_grub2(device, root_uuid, efi_system_part_uuid=None,
|
|||
# Add /usr/sbin to PATH variable to ensure it is there as we do
|
||||
# not use full path to grub binary anymore.
|
||||
path_variable = os.environ.get('PATH', '')
|
||||
path_variable = '%s:/bin:/usr/sbin' % path_variable
|
||||
path_variable = '%s:/bin:/usr/sbin:/sbin' % path_variable
|
||||
|
||||
# Install grub. Normally, grub goes to one disk only. In case of
|
||||
# md devices, grub goes to all underlying holder (RAID-1) disks.
|
||||
LOG.info("GRUB2 will be installed on disks %s", disks)
|
||||
for grub_disk in disks:
|
||||
LOG.debug("Installing GRUB2 on disk %s", grub_disk)
|
||||
utils.execute('chroot %(path)s /bin/sh -c '
|
||||
'"%(bin)s-install %(dev)s"' %
|
||||
{'path': path, 'bin': binary_name,
|
||||
'dev': grub_disk},
|
||||
shell=True, env_variables={'PATH': path_variable})
|
||||
LOG.debug("GRUB2 successfully installed on device %s", grub_disk)
|
||||
if efi_partitions:
|
||||
if not os.path.exists(efi_partition_mount_point):
|
||||
os.makedirs(efi_partition_mount_point)
|
||||
LOG.info("GRUB2 will be installed for UEFI on efi partitions %s",
|
||||
efi_partitions)
|
||||
for efi_partition in efi_partitions:
|
||||
utils.execute(
|
||||
'mount', efi_partition, efi_partition_mount_point)
|
||||
efi_mounted = True
|
||||
# FIXME(rg): does not work in cross boot mode case (target
|
||||
# boot mode differs from ramdisk one)
|
||||
# Probe for the correct target (depends on the arch, example
|
||||
# --target=x86_64-efi)
|
||||
utils.execute('chroot %(path)s /bin/sh -c '
|
||||
'"%(bin)s-install"' %
|
||||
{'path': path, 'bin': binary_name},
|
||||
shell=True,
|
||||
env_variables={
|
||||
'PATH': path_variable
|
||||
})
|
||||
# Also run grub-install with --removable, this installs grub to
|
||||
# the EFI fallback path. Useful if the NVRAM wasn't written
|
||||
# correctly, was reset or if testing with virt as libvirt
|
||||
# resets the NVRAM on instance start.
|
||||
# This operation is essentially a copy operation. Use of the
|
||||
# --removable flag, per the grub-install source code changes
|
||||
# the default file to be copied, destination file name, and
|
||||
# prevents NVRAM from being updated.
|
||||
# We only run grub2_install for uefi if we can't verify the
|
||||
# uefi bits
|
||||
utils.execute('chroot %(path)s /bin/sh -c '
|
||||
'"%(bin)s-install --removable"' %
|
||||
{'path': path, 'bin': binary_name},
|
||||
shell=True,
|
||||
env_variables={
|
||||
'PATH': path_variable
|
||||
})
|
||||
utils.execute('umount', efi_partition_mount_point, attempts=3,
|
||||
delay_on_retry=True)
|
||||
efi_mounted = False
|
||||
# NOTE: probably never needed for grub-mkconfig, does not hurt in
|
||||
# case of doubt, cleaned in the finally clause anyway
|
||||
utils.execute('mount', efi_partitions[0],
|
||||
efi_partition_mount_point)
|
||||
efi_mounted = True
|
||||
else:
|
||||
# FIXME(rg): does not work if ramdisk boot mode is not the same
|
||||
# as the target (--target=i386-pc, arch dependent).
|
||||
# See previous FIXME
|
||||
|
||||
# Also run grub-install with --removable, this installs grub to the
|
||||
# EFI fallback path. Useful if the NVRAM wasn't written correctly,
|
||||
# was reset or if testing with virt as libvirt resets the NVRAM
|
||||
# on instance start.
|
||||
# This operation is essentially a copy operation. Use of the
|
||||
# --removable flag, per the grub-install source code changes
|
||||
# the default file to be copied, destination file name, and
|
||||
# prevents NVRAM from being updated.
|
||||
# We only run grub2_install for uefi if we can't verify the uefi bits
|
||||
if efi_partition:
|
||||
utils.execute('chroot %(path)s /bin/sh -c '
|
||||
'"%(bin)s-install %(dev)s --removable"' %
|
||||
{'path': path, 'bin': binary_name, 'dev': device},
|
||||
shell=True, env_variables={'PATH': path_variable})
|
||||
# Install grub. Normally, grub goes to one disk only. In case of
|
||||
# md devices, grub goes to all underlying holder (RAID-1) disks.
|
||||
LOG.info("GRUB2 will be installed on disks %s", disks)
|
||||
for grub_disk in disks:
|
||||
LOG.debug("Installing GRUB2 on disk %s", grub_disk)
|
||||
utils.execute(
|
||||
'chroot %(path)s /bin/sh -c "%(bin)s-install %(dev)s"' %
|
||||
{
|
||||
'path': path,
|
||||
'bin': binary_name,
|
||||
'dev': grub_disk
|
||||
},
|
||||
shell=True,
|
||||
env_variables={
|
||||
'PATH': path_variable
|
||||
}
|
||||
)
|
||||
LOG.debug("GRUB2 successfully installed on device %s",
|
||||
grub_disk)
|
||||
|
||||
# If the image has dracut installed, set the rd.md.uuid kernel
|
||||
# parameter for discovered md devices.
|
||||
|
@ -527,7 +688,8 @@ class ImageExtension(base.BaseAgentExtension):
|
|||
|
||||
@base.sync_command('install_bootloader')
|
||||
def install_bootloader(self, root_uuid, efi_system_part_uuid=None,
|
||||
prep_boot_part_uuid=None):
|
||||
prep_boot_part_uuid=None,
|
||||
target_boot_mode='bios'):
|
||||
"""Install the GRUB2 bootloader on the image.
|
||||
|
||||
:param root_uuid: The UUID of the root partition.
|
||||
|
@ -537,6 +699,9 @@ class ImageExtension(base.BaseAgentExtension):
|
|||
:param prep_boot_part_uuid: The UUID of the PReP Boot partition.
|
||||
Used only for booting ppc64* partition images locally. In this
|
||||
scenario the bootloader will be installed here.
|
||||
:param target_boot_mode: bios, uefi. Only taken into account
|
||||
for softraid, when no efi partition is explicitely provided
|
||||
(happens for whole disk images)
|
||||
:raises: CommandExecutionError if the installation of the
|
||||
bootloader fails.
|
||||
:raises: DeviceNotFound if the root partition is not found.
|
||||
|
@ -545,7 +710,8 @@ class ImageExtension(base.BaseAgentExtension):
|
|||
device = hardware.dispatch_to_managers('get_os_install_device')
|
||||
iscsi.clean_up(device)
|
||||
boot = hardware.dispatch_to_managers('get_boot_info')
|
||||
if boot.current_boot_mode == 'uefi':
|
||||
if (boot.current_boot_mode == 'uefi'
|
||||
and not hardware.is_md_device(device)):
|
||||
has_efibootmgr = True
|
||||
try:
|
||||
utils.execute('efibootmgr', '--version')
|
||||
|
@ -563,4 +729,5 @@ class ImageExtension(base.BaseAgentExtension):
|
|||
_install_grub2(device,
|
||||
root_uuid=root_uuid,
|
||||
efi_system_part_uuid=efi_system_part_uuid,
|
||||
prep_boot_part_uuid=prep_boot_part_uuid)
|
||||
prep_boot_part_uuid=prep_boot_part_uuid,
|
||||
target_boot_mode=target_boot_mode)
|
||||
|
|
|
@ -1502,6 +1502,12 @@ class GenericHardwareManager(HardwareManager):
|
|||
devices.
|
||||
"""
|
||||
|
||||
# incr starts to 1
|
||||
# It means md0 is on the partition 1, md1 on 2...
|
||||
# incr could be incremented if we ever decide, for example to create
|
||||
# some additional partitions here (boot partitions)
|
||||
incr = 1
|
||||
|
||||
raid_config = node.get('target_raid_config', {})
|
||||
if not raid_config:
|
||||
LOG.debug("No target_raid_config found")
|
||||
|
@ -1544,23 +1550,50 @@ class GenericHardwareManager(HardwareManager):
|
|||
% ', '.join(with_parts))
|
||||
raise errors.SoftwareRAIDError(msg)
|
||||
|
||||
partition_table_type = utils.get_partition_table_type_from_specs(node)
|
||||
target_boot_mode = utils.get_node_boot_mode(node)
|
||||
|
||||
parted_start_dict = {}
|
||||
# Create an MBR partition table on each disk.
|
||||
# TODO(arne_wiebalck): Check if GPT would work as well.
|
||||
# Create a partition table on each disk.
|
||||
for dev_name in block_devices:
|
||||
LOG.info("Creating partition table on {}".format(dev_name))
|
||||
LOG.info("Creating partition table on {}".format(
|
||||
dev_name))
|
||||
try:
|
||||
utils.execute('parted', dev_name, '-s', '--',
|
||||
'mklabel', 'msdos')
|
||||
'mklabel', partition_table_type)
|
||||
except processutils.ProcessExecutionError as e:
|
||||
msg = "Failed to create partition table on {}: {}".format(
|
||||
dev_name, e)
|
||||
raise errors.SoftwareRAIDError(msg)
|
||||
|
||||
out, _u = utils.execute('sgdisk', '-F', dev_name)
|
||||
# May differ from 2048s, according to device geometry (example:
|
||||
# 4k disks).
|
||||
parted_start_dict[dev_name] = "%ss" % out.splitlines()[-1]
|
||||
# TODO(rg): TBD, several options regarding boot part slots here:
|
||||
# 1. Create boot partitions in prevision
|
||||
# 2. Just leave space
|
||||
# 3. Do nothing: rely on the caller to specify target_raid_config
|
||||
# correctly according to what they intend to do (eg not set MAX if
|
||||
# they know they will need some space for bios boot or efi parts.
|
||||
# (Best option imo, if we accept that the target volume granularity
|
||||
# is GiB, so you lose up to 1GiB just for a bios boot partition...)
|
||||
if target_boot_mode == 'uefi':
|
||||
# Leave 129MiB - start_sector s for the esp (approx 128MiB)
|
||||
# NOTE: any image efi partition is expected to be less
|
||||
# than 128MiB
|
||||
# TBD: 129MiB is a waste in most cases.
|
||||
raid_start = '129MiB'
|
||||
else:
|
||||
if partition_table_type == 'gpt':
|
||||
# Leave 8MiB - start_sector s (approx 7MiB)
|
||||
# for the bios boot partition or the ppc prepboot part
|
||||
# This should avoid grub errors saying that it cannot
|
||||
# install boot stage 1.5/2 (since the mbr gap does not
|
||||
# exist on disk holders with gpt tables)
|
||||
raid_start = '8MiB'
|
||||
else:
|
||||
# sgdisk works fine for display data on mbr tables too
|
||||
out, _u = utils.execute('sgdisk', '-F', dev_name)
|
||||
raid_start = "{}s".format(out.splitlines()[-1])
|
||||
|
||||
parted_start_dict[dev_name] = raid_start
|
||||
|
||||
LOG.debug("First available sectors per devices %s", parted_start_dict)
|
||||
|
||||
|
@ -1648,7 +1681,7 @@ class GenericHardwareManager(HardwareManager):
|
|||
if 'nvme' in device:
|
||||
part_delimiter = 'p'
|
||||
component_devices.append(
|
||||
device + part_delimiter + str(index + 1))
|
||||
device + part_delimiter + str(index + incr))
|
||||
raid_level = logical_disk['raid_level']
|
||||
# The schema check allows '1+0', but mdadm knows it as '10'.
|
||||
if raid_level == '1+0':
|
||||
|
|
|
@ -17,6 +17,7 @@ import os
|
|||
import shutil
|
||||
import tempfile
|
||||
|
||||
from ironic_lib import utils as ilib_utils
|
||||
import mock
|
||||
from oslo_concurrency import processutils
|
||||
|
||||
|
@ -59,7 +60,9 @@ class TestImageExtension(base.IronicAgentTest):
|
|||
self.assertEqual(2, mock_dispatch.call_count)
|
||||
mock_grub2.assert_called_once_with(
|
||||
self.fake_dev, root_uuid=self.fake_root_uuid,
|
||||
efi_system_part_uuid=None, prep_boot_part_uuid=None)
|
||||
efi_system_part_uuid=None, prep_boot_part_uuid=None,
|
||||
target_boot_mode='bios'
|
||||
)
|
||||
mock_iscsi_clean.assert_called_once_with(self.fake_dev)
|
||||
|
||||
@mock.patch.object(iscsi, 'clean_up', autospec=True)
|
||||
|
@ -74,7 +77,9 @@ class TestImageExtension(base.IronicAgentTest):
|
|||
mock_uefi.return_value = False
|
||||
self.agent_extension.install_bootloader(
|
||||
root_uuid=self.fake_root_uuid,
|
||||
efi_system_part_uuid=self.fake_efi_system_part_uuid)
|
||||
efi_system_part_uuid=self.fake_efi_system_part_uuid,
|
||||
target_boot_mode='uefi'
|
||||
)
|
||||
mock_dispatch.assert_any_call('get_os_install_device')
|
||||
mock_dispatch.assert_any_call('get_boot_info')
|
||||
self.assertEqual(2, mock_dispatch.call_count)
|
||||
|
@ -82,9 +87,12 @@ class TestImageExtension(base.IronicAgentTest):
|
|||
self.fake_dev,
|
||||
root_uuid=self.fake_root_uuid,
|
||||
efi_system_part_uuid=self.fake_efi_system_part_uuid,
|
||||
prep_boot_part_uuid=None)
|
||||
prep_boot_part_uuid=None,
|
||||
target_boot_mode='uefi'
|
||||
)
|
||||
mock_iscsi_clean.assert_called_once_with(self.fake_dev)
|
||||
|
||||
@mock.patch.object(hardware, 'is_md_device', lambda *_: False)
|
||||
@mock.patch.object(os.path, 'exists', lambda *_: False)
|
||||
@mock.patch.object(iscsi, 'clean_up', autospec=True)
|
||||
@mock.patch.object(image, '_get_efi_bootloaders', autospec=True)
|
||||
|
@ -133,6 +141,7 @@ class TestImageExtension(base.IronicAgentTest):
|
|||
mock_utils_efi_part.assert_called_once_with(self.fake_dev)
|
||||
self.assertEqual(8, mock_execute.call_count)
|
||||
|
||||
@mock.patch.object(hardware, 'is_md_device', lambda *_: False)
|
||||
@mock.patch.object(os.path, 'exists', lambda *_: False)
|
||||
@mock.patch.object(iscsi, 'clean_up', autospec=True)
|
||||
@mock.patch.object(image, '_get_efi_bootloaders', autospec=True)
|
||||
|
@ -180,6 +189,7 @@ class TestImageExtension(base.IronicAgentTest):
|
|||
mock_utils_efi_part.assert_called_once_with(self.fake_dev)
|
||||
self.assertEqual(8, mock_execute.call_count)
|
||||
|
||||
@mock.patch.object(hardware, 'is_md_device', lambda *_: False)
|
||||
@mock.patch.object(os.path, 'exists', lambda *_: False)
|
||||
@mock.patch.object(iscsi, 'clean_up', autospec=True)
|
||||
@mock.patch.object(image, '_get_efi_bootloaders', autospec=True)
|
||||
|
@ -234,6 +244,7 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
|
|||
mock_utils_efi_part.assert_called_once_with(self.fake_dev)
|
||||
self.assertEqual(10, mock_execute.call_count)
|
||||
|
||||
@mock.patch.object(hardware, 'is_md_device', lambda *_: False)
|
||||
@mock.patch.object(os.path, 'exists', lambda *_: False)
|
||||
@mock.patch.object(iscsi, 'clean_up', autospec=True)
|
||||
@mock.patch.object(image, '_get_efi_bootloaders', autospec=True)
|
||||
|
@ -306,9 +317,12 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
|
|||
self.fake_dev,
|
||||
root_uuid=self.fake_root_uuid,
|
||||
efi_system_part_uuid=None,
|
||||
prep_boot_part_uuid=self.fake_prep_boot_part_uuid)
|
||||
prep_boot_part_uuid=self.fake_prep_boot_part_uuid,
|
||||
target_boot_mode='bios'
|
||||
)
|
||||
mock_iscsi_clean.assert_called_once_with(self.fake_dev)
|
||||
|
||||
@mock.patch.object(hardware, 'is_md_device', lambda *_: False)
|
||||
@mock.patch.object(os.path, 'exists', lambda *_: False)
|
||||
@mock.patch.object(iscsi, 'clean_up', autospec=True)
|
||||
def test_install_bootloader_failure(self, mock_iscsi_clean, mock_execute,
|
||||
|
@ -350,12 +364,14 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
|
|||
mock.call(('chroot %s /bin/sh -c '
|
||||
'"grub-install %s"' %
|
||||
(self.fake_dir, self.fake_dev)), shell=True,
|
||||
env_variables={'PATH': '/sbin:/bin:/usr/sbin'}),
|
||||
env_variables={
|
||||
'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
|
||||
mock.call(('chroot %s /bin/sh -c '
|
||||
'"grub-mkconfig -o '
|
||||
'/boot/grub/grub.cfg"' % self.fake_dir),
|
||||
shell=True,
|
||||
env_variables={'PATH': '/sbin:/bin:/usr/sbin'}),
|
||||
env_variables={
|
||||
'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
|
||||
mock.call('umount', self.fake_dir + '/dev',
|
||||
attempts=3, delay_on_retry=True),
|
||||
mock.call('umount', self.fake_dir + '/proc',
|
||||
|
@ -400,12 +416,14 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
|
|||
'"grub-install %s"' %
|
||||
(self.fake_dir, self.fake_prep_boot_part)),
|
||||
shell=True,
|
||||
env_variables={'PATH': '/sbin:/bin:/usr/sbin'}),
|
||||
env_variables={
|
||||
'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
|
||||
mock.call(('chroot %s /bin/sh -c '
|
||||
'"grub-mkconfig -o '
|
||||
'/boot/grub/grub.cfg"' % self.fake_dir),
|
||||
shell=True,
|
||||
env_variables={'PATH': '/sbin:/bin:/usr/sbin'}),
|
||||
env_variables={
|
||||
'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
|
||||
mock.call('umount', self.fake_dir + '/dev',
|
||||
attempts=3, delay_on_retry=True),
|
||||
mock.call('umount', self.fake_dir + '/proc',
|
||||
|
@ -442,7 +460,8 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
|
|||
|
||||
image._install_grub2(
|
||||
self.fake_dev, root_uuid=self.fake_root_uuid,
|
||||
efi_system_part_uuid=self.fake_efi_system_part_uuid)
|
||||
efi_system_part_uuid=self.fake_efi_system_part_uuid,
|
||||
target_boot_mode='uefi')
|
||||
|
||||
expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
|
||||
mock.call('mount', '-o', 'bind', '/dev',
|
||||
|
@ -455,19 +474,26 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
|
|||
self.fake_dir + '/sys'),
|
||||
mock.call('mount', self.fake_efi_system_part,
|
||||
self.fake_dir + '/boot/efi'),
|
||||
mock.call(('chroot %s /bin/sh -c "grub-install"' %
|
||||
self.fake_dir), shell=True,
|
||||
env_variables={
|
||||
'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
|
||||
mock.call(('chroot %s /bin/sh -c '
|
||||
'"grub-install %s"' %
|
||||
(self.fake_dir, self.fake_dev)), shell=True,
|
||||
env_variables={'PATH': '/sbin:/bin:/usr/sbin'}),
|
||||
mock.call(('chroot %s /bin/sh -c '
|
||||
'"grub-install %s --removable"' %
|
||||
(self.fake_dir, self.fake_dev)), shell=True,
|
||||
env_variables={'PATH': '/sbin:/bin:/usr/sbin'}),
|
||||
'"grub-install --removable"' %
|
||||
self.fake_dir), shell=True,
|
||||
env_variables={
|
||||
'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
|
||||
mock.call(
|
||||
'umount', self.fake_dir + '/boot/efi',
|
||||
attempts=3, delay_on_retry=True),
|
||||
mock.call('mount', self.fake_efi_system_part,
|
||||
'/tmp/fake-dir/boot/efi'),
|
||||
mock.call(('chroot %s /bin/sh -c '
|
||||
'"grub-mkconfig -o '
|
||||
'/boot/grub/grub.cfg"' % self.fake_dir),
|
||||
shell=True,
|
||||
env_variables={'PATH': '/sbin:/bin:/usr/sbin'}),
|
||||
env_variables={
|
||||
'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
|
||||
mock.call('umount', self.fake_dir + '/boot/efi',
|
||||
attempts=3, delay_on_retry=True),
|
||||
mock.call('umount', self.fake_dir + '/dev',
|
||||
|
@ -525,21 +551,22 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
|
|||
self.fake_dir + '/sys'),
|
||||
mock.call('mount', self.fake_efi_system_part,
|
||||
self.fake_dir + '/boot/efi'),
|
||||
mock.call(('chroot %s /bin/sh -c "grub-install"' %
|
||||
self.fake_dir), shell=True,
|
||||
env_variables={
|
||||
'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
|
||||
mock.call(('chroot %s /bin/sh -c '
|
||||
'"grub-install %s"' %
|
||||
(self.fake_dir, self.fake_dev)), shell=True,
|
||||
env_variables={'PATH': '/sbin:/bin:/usr/sbin'}),
|
||||
mock.call(('chroot %s /bin/sh -c '
|
||||
'"grub-install %s --removable"' %
|
||||
(self.fake_dir, self.fake_dev)), shell=True,
|
||||
env_variables={'PATH': '/sbin:/bin:/usr/sbin'}),
|
||||
mock.call(('chroot %s /bin/sh -c '
|
||||
'"grub-mkconfig -o '
|
||||
'/boot/grub/grub.cfg"' % self.fake_dir),
|
||||
'"grub-install --removable"' % self.fake_dir),
|
||||
shell=True,
|
||||
env_variables={'PATH': '/sbin:/bin:/usr/sbin'}),
|
||||
env_variables={
|
||||
'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
|
||||
# Call from for loop
|
||||
mock.call('umount', self.fake_dir + '/boot/efi',
|
||||
attempts=3, delay_on_retry=True)]
|
||||
attempts=3, delay_on_retry=True),
|
||||
# Call from finally
|
||||
mock.call('umount', self.fake_dir + '/boot/efi',
|
||||
attempts=3, delay_on_retry=True)
|
||||
]
|
||||
mock_execute.assert_has_calls(expected)
|
||||
|
||||
@mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False)
|
||||
|
@ -595,6 +622,374 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
|
|||
uuid=self.fake_root_uuid)
|
||||
self.assertFalse(mock_dispatch.called)
|
||||
|
||||
@mock.patch.object(utils, 'get_efi_part_on_device', autospec=True)
|
||||
def test__prepare_boot_partitions_for_softraid_uefi_gpt(
|
||||
self, mock_efi_part, mock_execute, mock_dispatch):
|
||||
mock_efi_part.return_value = '12'
|
||||
mock_execute.side_effect = [
|
||||
('451', None), # sgdisk -F
|
||||
(None, None), # sgdisk create part
|
||||
(None, None), # partprobe
|
||||
(None, None), # blkid
|
||||
('/dev/sda12: dsfkgsdjfg', None), # blkid
|
||||
(None, None), # cp
|
||||
('452', None), # sgdisk -F
|
||||
(None, None), # sgdisk create part
|
||||
(None, None), # partprobe
|
||||
(None, None), # blkid
|
||||
('/dev/sdb14: whatever', None), # blkid
|
||||
(None, None), # cp
|
||||
]
|
||||
|
||||
efi_parts = image._prepare_boot_partitions_for_softraid(
|
||||
'/dev/md0', ['/dev/sda', '/dev/sdb'], None,
|
||||
target_boot_mode='uefi')
|
||||
|
||||
mock_efi_part.assert_called_once_with('/dev/md0')
|
||||
expected = [
|
||||
mock.call('sgdisk', '-F', '/dev/sda'),
|
||||
mock.call('sgdisk', '-n', '0:451s:+128MiB', '-t', '0:ef00', '-c',
|
||||
'0:uefi-holder-0', '/dev/sda'),
|
||||
mock.call('partprobe'),
|
||||
mock.call('blkid'),
|
||||
mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-0',
|
||||
'/dev/sda'),
|
||||
mock.call('cp', '/dev/md0p12', '/dev/sda12'),
|
||||
mock.call('sgdisk', '-F', '/dev/sdb'),
|
||||
mock.call('sgdisk', '-n', '0:452s:+128MiB', '-t', '0:ef00', '-c',
|
||||
'0:uefi-holder-1', '/dev/sdb'),
|
||||
mock.call('partprobe'),
|
||||
mock.call('blkid'),
|
||||
mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-1',
|
||||
'/dev/sdb'),
|
||||
mock.call('cp', '/dev/md0p12', '/dev/sdb14')
|
||||
]
|
||||
mock_execute.assert_has_calls(expected, any_order=False)
|
||||
self.assertEqual(efi_parts, ['/dev/sda12', '/dev/sdb14'])
|
||||
|
||||
@mock.patch.object(utils, 'get_efi_part_on_device', autospec=True)
|
||||
@mock.patch.object(ilib_utils, 'mkfs', autospec=True)
|
||||
def test__prepare_boot_partitions_for_softraid_uefi_gpt_esp_not_found(
|
||||
self, mock_mkfs, mock_efi_part, mock_execute, mock_dispatch):
|
||||
mock_efi_part.return_value = None
|
||||
mock_execute.side_effect = [
|
||||
('451', None), # sgdisk -F
|
||||
(None, None), # sgdisk create part
|
||||
(None, None), # partprobe
|
||||
(None, None), # blkid
|
||||
('/dev/sda12: dsfkgsdjfg', None), # blkid
|
||||
('452', None), # sgdisk -F
|
||||
(None, None), # sgdisk create part
|
||||
(None, None), # partprobe
|
||||
(None, None), # blkid
|
||||
('/dev/sdb14: whatever', None), # blkid
|
||||
]
|
||||
|
||||
efi_parts = image._prepare_boot_partitions_for_softraid(
|
||||
'/dev/md0', ['/dev/sda', '/dev/sdb'], None,
|
||||
target_boot_mode='uefi')
|
||||
|
||||
mock_efi_part.assert_called_once_with('/dev/md0')
|
||||
expected = [
|
||||
mock.call('sgdisk', '-F', '/dev/sda'),
|
||||
mock.call('sgdisk', '-n', '0:451s:+128MiB', '-t', '0:ef00', '-c',
|
||||
'0:uefi-holder-0', '/dev/sda'),
|
||||
mock.call('partprobe'),
|
||||
mock.call('blkid'),
|
||||
mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-0',
|
||||
'/dev/sda'),
|
||||
mock.call('sgdisk', '-F', '/dev/sdb'),
|
||||
mock.call('sgdisk', '-n', '0:452s:+128MiB', '-t', '0:ef00', '-c',
|
||||
'0:uefi-holder-1', '/dev/sdb'),
|
||||
mock.call('partprobe'),
|
||||
mock.call('blkid'),
|
||||
mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-1',
|
||||
'/dev/sdb'),
|
||||
]
|
||||
mock_execute.assert_has_calls(expected, any_order=False)
|
||||
mock_mkfs.assert_has_calls([
|
||||
mock.call(path='/dev/sda12', label='efi-part', fs='vfat'),
|
||||
mock.call(path='/dev/sdb14', label='efi-part-b', fs='vfat'),
|
||||
], any_order=False)
|
||||
self.assertEqual(efi_parts, ['/dev/sda12', '/dev/sdb14'])
|
||||
|
||||
def test__prepare_boot_partitions_for_softraid_uefi_gpt_efi_provided(
|
||||
self, mock_execute, mock_dispatch):
|
||||
mock_execute.side_effect = [
|
||||
('451', None), # sgdisk -F
|
||||
(None, None), # sgdisk create part
|
||||
(None, None), # partprobe
|
||||
(None, None), # blkid
|
||||
('/dev/sda12: dsfkgsdjfg', None), # blkid
|
||||
(None, None), # cp
|
||||
('452', None), # sgdisk -F
|
||||
(None, None), # sgdisk create part
|
||||
(None, None), # partprobe
|
||||
(None, None), # blkid
|
||||
('/dev/sdb14: whatever', None), # blkid
|
||||
(None, None), # cp
|
||||
]
|
||||
|
||||
efi_parts = image._prepare_boot_partitions_for_softraid(
|
||||
'/dev/md0', ['/dev/sda', '/dev/sdb'], '/dev/md0p15',
|
||||
target_boot_mode='uefi')
|
||||
|
||||
expected = [
|
||||
mock.call('sgdisk', '-F', '/dev/sda'),
|
||||
mock.call('sgdisk', '-n', '0:451s:+128MiB', '-t', '0:ef00', '-c',
|
||||
'0:uefi-holder-0', '/dev/sda'),
|
||||
mock.call('partprobe'),
|
||||
mock.call('blkid'),
|
||||
mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-0',
|
||||
'/dev/sda'),
|
||||
mock.call('cp', '/dev/md0p15', '/dev/sda12'),
|
||||
mock.call('sgdisk', '-F', '/dev/sdb'),
|
||||
mock.call('sgdisk', '-n', '0:452s:+128MiB', '-t', '0:ef00', '-c',
|
||||
'0:uefi-holder-1', '/dev/sdb'),
|
||||
mock.call('partprobe'),
|
||||
mock.call('blkid'),
|
||||
mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-1',
|
||||
'/dev/sdb'),
|
||||
mock.call('cp', '/dev/md0p15', '/dev/sdb14')
|
||||
]
|
||||
mock_execute.assert_has_calls(expected, any_order=False)
|
||||
self.assertEqual(efi_parts, ['/dev/sda12', '/dev/sdb14'])
|
||||
|
||||
@mock.patch.object(utils, 'scan_partition_table_type', autospec=True,
|
||||
return_value='msdos')
|
||||
def test__prepare_boot_partitions_for_softraid_bios_msdos(
|
||||
self, mock_label_scan, mock_execute, mock_dispatch):
|
||||
|
||||
efi_parts = image._prepare_boot_partitions_for_softraid(
|
||||
'/dev/md0', ['/dev/sda', '/dev/sdb'], 'notusedanyway',
|
||||
target_boot_mode='bios')
|
||||
|
||||
expected = [
|
||||
mock.call('/dev/sda'),
|
||||
mock.call('/dev/sdb'),
|
||||
]
|
||||
mock_label_scan.assert_has_calls(expected, any_order=False)
|
||||
self.assertEqual(efi_parts, [])
|
||||
|
||||
@mock.patch.object(utils, 'scan_partition_table_type', autospec=True,
|
||||
return_value='gpt')
|
||||
def test__prepare_boot_partitions_for_softraid_bios_gpt(
|
||||
self, mock_label_scan, mock_execute, mock_dispatch):
|
||||
|
||||
mock_execute.side_effect = [
|
||||
('whatever\n314', None), # sgdisk -F
|
||||
(None, None), # bios boot grub
|
||||
('warning message\n914', None), # sgdisk -F
|
||||
(None, None), # bios boot grub
|
||||
]
|
||||
|
||||
efi_parts = image._prepare_boot_partitions_for_softraid(
|
||||
'/dev/md0', ['/dev/sda', '/dev/sdb'], 'notusedanyway',
|
||||
target_boot_mode='bios')
|
||||
|
||||
expected_scan = [
|
||||
mock.call('/dev/sda'),
|
||||
mock.call('/dev/sdb'),
|
||||
]
|
||||
|
||||
mock_label_scan.assert_has_calls(expected_scan, any_order=False)
|
||||
|
||||
expected_exec = [
|
||||
mock.call('sgdisk', '-F', '/dev/sda'),
|
||||
mock.call('sgdisk', '-n', '0:314s:+2MiB', '-t', '0:ef02', '-c',
|
||||
'0:bios-boot-part-0', '/dev/sda'),
|
||||
mock.call('sgdisk', '-F', '/dev/sdb'),
|
||||
mock.call('sgdisk', '-n', '0:914s:+2MiB', '-t', '0:ef02', '-c',
|
||||
'0:bios-boot-part-1', '/dev/sdb'),
|
||||
]
|
||||
|
||||
mock_execute.assert_has_calls(expected_exec, any_order=False)
|
||||
self.assertEqual(efi_parts, [])
|
||||
|
||||
@mock.patch.object(image, '_is_bootloader_loaded', lambda *_: True)
|
||||
@mock.patch.object(hardware, 'is_md_device', autospec=True)
|
||||
@mock.patch.object(hardware, 'md_restart', autospec=True)
|
||||
@mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
|
||||
@mock.patch.object(hardware, 'get_holder_disks', autospec=True,
|
||||
return_value=['/dev/sda', '/dev/sdb'])
|
||||
@mock.patch.object(os, 'environ', autospec=True)
|
||||
@mock.patch.object(os, 'makedirs', autospec=True)
|
||||
@mock.patch.object(image, '_get_partition', autospec=True)
|
||||
@mock.patch.object(image, '_prepare_boot_partitions_for_softraid',
|
||||
autospec=True,
|
||||
return_value=['/dev/sda1', '/dev/sdb2'])
|
||||
@mock.patch.object(image, '_has_dracut',
|
||||
autospec=True,
|
||||
return_value=False)
|
||||
def test__install_grub2_softraid_uefi_gpt(
|
||||
self, mock_dracut,
|
||||
mock_prepare, mock_get_part_uuid, mkdir_mock, environ_mock,
|
||||
mock_holder, mock_md_get_raid_devices, mock_restart,
|
||||
mock_is_md_device,
|
||||
mock_execute, mock_dispatch):
|
||||
|
||||
mock_get_part_uuid.side_effect = [self.fake_root_part,
|
||||
self.fake_efi_system_part]
|
||||
environ_mock.get.return_value = '/sbin'
|
||||
mock_is_md_device.return_value = True
|
||||
mock_md_get_raid_devices.return_value = {}
|
||||
|
||||
image._install_grub2(
|
||||
self.fake_dev, root_uuid=self.fake_root_uuid,
|
||||
efi_system_part_uuid=self.fake_efi_system_part_uuid,
|
||||
target_boot_mode='uefi')
|
||||
|
||||
expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
|
||||
mock.call('mount', '-o', 'bind', '/dev',
|
||||
self.fake_dir + '/dev'),
|
||||
mock.call('mount', '-o', 'bind', '/proc',
|
||||
self.fake_dir + '/proc'),
|
||||
mock.call('mount', '-o', 'bind', '/run',
|
||||
self.fake_dir + '/run'),
|
||||
mock.call('mount', '-t', 'sysfs', 'none',
|
||||
self.fake_dir + '/sys'),
|
||||
mock.call('mount', '/dev/sda1',
|
||||
self.fake_dir + '/boot/efi'),
|
||||
mock.call(('chroot %s /bin/sh -c "grub-install"' %
|
||||
self.fake_dir), shell=True,
|
||||
env_variables={
|
||||
'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
|
||||
mock.call(('chroot %s /bin/sh -c '
|
||||
'"grub-install --removable"' %
|
||||
self.fake_dir), shell=True,
|
||||
env_variables={
|
||||
'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
|
||||
mock.call(
|
||||
'umount', self.fake_dir + '/boot/efi',
|
||||
attempts=3, delay_on_retry=True),
|
||||
mock.call('mount', '/dev/sdb2',
|
||||
self.fake_dir + '/boot/efi'),
|
||||
mock.call(('chroot %s /bin/sh -c "grub-install"' %
|
||||
self.fake_dir), shell=True,
|
||||
env_variables={
|
||||
'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
|
||||
mock.call(('chroot %s /bin/sh -c '
|
||||
'"grub-install --removable"' %
|
||||
self.fake_dir), shell=True,
|
||||
env_variables={
|
||||
'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
|
||||
mock.call(
|
||||
'umount', self.fake_dir + '/boot/efi',
|
||||
attempts=3, delay_on_retry=True),
|
||||
mock.call('mount', '/dev/sda1',
|
||||
'/tmp/fake-dir/boot/efi'),
|
||||
mock.call(('chroot %s /bin/sh -c '
|
||||
'"grub-mkconfig -o '
|
||||
'/boot/grub/grub.cfg"' % self.fake_dir),
|
||||
shell=True,
|
||||
env_variables={
|
||||
'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
|
||||
mock.call('umount', self.fake_dir + '/boot/efi',
|
||||
attempts=3, delay_on_retry=True),
|
||||
mock.call('umount', self.fake_dir + '/dev',
|
||||
attempts=3, delay_on_retry=True),
|
||||
mock.call('umount', self.fake_dir + '/proc',
|
||||
attempts=3, delay_on_retry=True),
|
||||
mock.call('umount', self.fake_dir + '/run',
|
||||
attempts=3, delay_on_retry=True),
|
||||
mock.call('umount', self.fake_dir + '/sys',
|
||||
attempts=3, delay_on_retry=True),
|
||||
mock.call('umount', self.fake_dir, attempts=3,
|
||||
delay_on_retry=True)]
|
||||
mock_execute.assert_has_calls(expected)
|
||||
mock_get_part_uuid.assert_any_call(self.fake_dev,
|
||||
uuid=self.fake_root_uuid)
|
||||
mock_get_part_uuid.assert_any_call(self.fake_dev,
|
||||
uuid=self.fake_efi_system_part_uuid)
|
||||
self.assertFalse(mock_dispatch.called)
|
||||
mock_prepare.assert_called_once_with(self.fake_dev,
|
||||
['/dev/sda', '/dev/sdb'],
|
||||
self.fake_efi_system_part, 'uefi')
|
||||
mock_restart.assert_called_once_with(self.fake_dev)
|
||||
mock_holder.assert_called_once_with(self.fake_dev)
|
||||
mock_dracut.assert_called_once_with(self.fake_dir)
|
||||
|
||||
@mock.patch.object(image, '_is_bootloader_loaded', lambda *_: True)
|
||||
@mock.patch.object(hardware, 'is_md_device', autospec=True)
|
||||
@mock.patch.object(hardware, 'md_restart', autospec=True)
|
||||
@mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
|
||||
@mock.patch.object(hardware, 'get_holder_disks', autospec=True,
|
||||
return_value=['/dev/sda', '/dev/sdb'])
|
||||
@mock.patch.object(os, 'environ', autospec=True)
|
||||
@mock.patch.object(os, 'makedirs', autospec=True)
|
||||
@mock.patch.object(image, '_get_partition', autospec=True)
|
||||
@mock.patch.object(image, '_prepare_boot_partitions_for_softraid',
|
||||
autospec=True,
|
||||
return_value=[])
|
||||
@mock.patch.object(image, '_has_dracut',
|
||||
autospec=True,
|
||||
return_value=False)
|
||||
def test__install_grub2_softraid_bios(
|
||||
self, mock_dracut,
|
||||
mock_prepare, mock_get_part_uuid, mkdir_mock, environ_mock,
|
||||
mock_holder, mock_md_get_raid_devices, mock_restart,
|
||||
mock_is_md_device,
|
||||
mock_execute, mock_dispatch):
|
||||
|
||||
mock_get_part_uuid.side_effect = [self.fake_root_part,
|
||||
self.fake_efi_system_part]
|
||||
environ_mock.get.return_value = '/sbin'
|
||||
mock_is_md_device.return_value = True
|
||||
mock_md_get_raid_devices.return_value = {}
|
||||
|
||||
image._install_grub2(
|
||||
self.fake_dev, root_uuid=self.fake_root_uuid,
|
||||
efi_system_part_uuid=None,
|
||||
target_boot_mode='bios')
|
||||
|
||||
expected = [
|
||||
mock.call('mount', '/dev/fake2', self.fake_dir),
|
||||
mock.call('mount', '-o', 'bind', '/dev',
|
||||
self.fake_dir + '/dev'),
|
||||
mock.call('mount', '-o', 'bind', '/proc',
|
||||
self.fake_dir + '/proc'),
|
||||
mock.call('mount', '-o', 'bind', '/run',
|
||||
self.fake_dir + '/run'),
|
||||
mock.call('mount', '-t', 'sysfs', 'none',
|
||||
self.fake_dir + '/sys'),
|
||||
mock.call(('chroot %s /bin/sh -c '
|
||||
'"grub-install %s"' %
|
||||
(self.fake_dir, '/dev/sda')), shell=True,
|
||||
env_variables={
|
||||
'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
|
||||
mock.call(('chroot %s /bin/sh -c '
|
||||
'"grub-install %s"' %
|
||||
(self.fake_dir, '/dev/sdb')), shell=True,
|
||||
env_variables={
|
||||
'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
|
||||
mock.call(('chroot %s /bin/sh -c '
|
||||
'"grub-mkconfig -o '
|
||||
'/boot/grub/grub.cfg"' % self.fake_dir),
|
||||
shell=True,
|
||||
env_variables={
|
||||
'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
|
||||
mock.call('umount', self.fake_dir + '/dev',
|
||||
attempts=3, delay_on_retry=True),
|
||||
mock.call('umount', self.fake_dir + '/proc',
|
||||
attempts=3, delay_on_retry=True),
|
||||
mock.call('umount', self.fake_dir + '/run',
|
||||
attempts=3, delay_on_retry=True),
|
||||
mock.call('umount', self.fake_dir + '/sys',
|
||||
attempts=3, delay_on_retry=True),
|
||||
mock.call('umount', self.fake_dir, attempts=3,
|
||||
delay_on_retry=True)]
|
||||
self.assertFalse(mkdir_mock.called)
|
||||
mock_execute.assert_has_calls(expected)
|
||||
mock_get_part_uuid.assert_any_call(self.fake_dev,
|
||||
uuid=self.fake_root_uuid)
|
||||
self.assertFalse(mock_dispatch.called)
|
||||
mock_prepare.assert_called_once_with(self.fake_dev,
|
||||
['/dev/sda', '/dev/sdb'],
|
||||
None, 'bios')
|
||||
mock_restart.assert_called_once_with(self.fake_dev)
|
||||
mock_holder.assert_called_once_with(self.fake_dev)
|
||||
mock_dracut.assert_called_once_with(self.fake_dir)
|
||||
|
||||
@mock.patch.object(image, '_is_bootloader_loaded', autospec=True)
|
||||
@mock.patch.object(hardware, 'is_md_device', autospec=True)
|
||||
def test__get_partition(self, mock_is_md_device, mock_is_bootloader,
|
||||
|
|
|
@ -2781,8 +2781,11 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||
|
||||
@mock.patch.object(disk_utils, 'list_partitions', autospec=True)
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_create_configuration(self, mocked_execute, mock_list_parts):
|
||||
@mock.patch.object(os.path, 'isdir', autospec=True, return_value=False)
|
||||
def test_create_configuration(self, mocked_os_path_isdir, mocked_execute,
|
||||
mock_list_parts):
|
||||
node = self.node
|
||||
|
||||
raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
|
@ -2820,13 +2823,13 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||
]
|
||||
|
||||
result = self.hardware.create_configuration(node, [])
|
||||
|
||||
mocked_os_path_isdir.assert_has_calls([
|
||||
mock.call('/sys/firmware/efi')
|
||||
])
|
||||
mocked_execute.assert_has_calls([
|
||||
mock.call('parted', '/dev/sda', '-s', '--', 'mklabel',
|
||||
'msdos'),
|
||||
mock.call('parted', '/dev/sda', '-s', '--', 'mklabel', 'msdos'),
|
||||
mock.call('sgdisk', '-F', '/dev/sda'),
|
||||
mock.call('parted', '/dev/sdb', '-s', '--', 'mklabel',
|
||||
'msdos'),
|
||||
mock.call('parted', '/dev/sdb', '-s', '--', 'mklabel', 'msdos'),
|
||||
mock.call('sgdisk', '-F', '/dev/sdb'),
|
||||
mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--',
|
||||
'mkpart', 'primary', '42s', '10GiB'),
|
||||
|
@ -3034,7 +3037,144 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||
@mock.patch.object(disk_utils, 'list_partitions', autospec=True,
|
||||
return_value=[])
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_create_configuration_no_max(self, mocked_execute,
|
||||
@mock.patch.object(os.path, 'isdir', autospec=True, return_value=True)
|
||||
def test_create_configuration_efi(self, mocked_os_path_isdir,
|
||||
mocked_execute, mock_list_parts):
|
||||
node = self.node
|
||||
|
||||
raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
"size_gb": "10",
|
||||
"raid_level": "1",
|
||||
"controller": "software",
|
||||
},
|
||||
{
|
||||
"size_gb": "MAX",
|
||||
"raid_level": "0",
|
||||
"controller": "software",
|
||||
},
|
||||
]
|
||||
}
|
||||
node['target_raid_config'] = raid_config
|
||||
device1 = hardware.BlockDevice('/dev/sda', 'sda', 107374182400, True)
|
||||
device2 = hardware.BlockDevice('/dev/sdb', 'sdb', 107374182400, True)
|
||||
self.hardware.list_block_devices = mock.Mock()
|
||||
self.hardware.list_block_devices.return_value = [device1, device2]
|
||||
|
||||
mocked_execute.side_effect = [
|
||||
None, # mklabel sda
|
||||
None, # mklabel sda
|
||||
None, None, # parted + partx sda
|
||||
None, None, # parted + partx sdb
|
||||
None, None, # parted + partx sda
|
||||
None, None, # parted + partx sdb
|
||||
None, None # mdadms
|
||||
]
|
||||
|
||||
result = self.hardware.create_configuration(node, [])
|
||||
mocked_os_path_isdir.assert_has_calls([
|
||||
mock.call('/sys/firmware/efi')
|
||||
])
|
||||
mocked_execute.assert_has_calls([
|
||||
mock.call('parted', '/dev/sda', '-s', '--', 'mklabel', 'gpt'),
|
||||
mock.call('parted', '/dev/sdb', '-s', '--', 'mklabel', 'gpt'),
|
||||
mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--',
|
||||
'mkpart', 'primary', '129MiB', '10GiB'),
|
||||
mock.call('partx', '-u', '/dev/sda', check_exit_code=False),
|
||||
mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--',
|
||||
'mkpart', 'primary', '129MiB', '10GiB'),
|
||||
mock.call('partx', '-u', '/dev/sdb', check_exit_code=False),
|
||||
mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--',
|
||||
'mkpart', 'primary', '10GiB', '-1'),
|
||||
mock.call('partx', '-u', '/dev/sda', check_exit_code=False),
|
||||
mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--',
|
||||
'mkpart', 'primary', '10GiB', '-1'),
|
||||
mock.call('partx', '-u', '/dev/sdb', check_exit_code=False),
|
||||
mock.call('mdadm', '--create', '/dev/md0', '--force', '--run',
|
||||
'--metadata=1', '--level', '1', '--raid-devices', 2,
|
||||
'/dev/sda1', '/dev/sdb1'),
|
||||
mock.call('mdadm', '--create', '/dev/md1', '--force', '--run',
|
||||
'--metadata=1', '--level', '0', '--raid-devices', 2,
|
||||
'/dev/sda2', '/dev/sdb2')])
|
||||
self.assertEqual(raid_config, result)
|
||||
|
||||
@mock.patch.object(disk_utils, 'list_partitions', autospec=True,
|
||||
return_value=[])
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
@mock.patch.object(os.path, 'isdir', autospec=True, return_value=False)
|
||||
def test_create_configuration_force_gpt_with_disk_label(
|
||||
self, mocked_os_path_isdir, mocked_execute, mock_list_part):
|
||||
node = self.node
|
||||
|
||||
raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
"size_gb": "10",
|
||||
"raid_level": "1",
|
||||
"controller": "software",
|
||||
},
|
||||
{
|
||||
"size_gb": "MAX",
|
||||
"raid_level": "0",
|
||||
"controller": "software",
|
||||
},
|
||||
]
|
||||
}
|
||||
node['target_raid_config'] = raid_config
|
||||
node['properties'] = {
|
||||
'capabilities': {
|
||||
'disk_label': 'gpt'
|
||||
}
|
||||
}
|
||||
|
||||
device1 = hardware.BlockDevice('/dev/sda', 'sda', 107374182400, True)
|
||||
device2 = hardware.BlockDevice('/dev/sdb', 'sdb', 107374182400, True)
|
||||
self.hardware.list_block_devices = mock.Mock()
|
||||
self.hardware.list_block_devices.return_value = [device1, device2]
|
||||
|
||||
mocked_execute.side_effect = [
|
||||
None, # mklabel sda
|
||||
None, # mklabel sda
|
||||
None, None, # parted + partx sda
|
||||
None, None, # parted + partx sdb
|
||||
None, None, # parted + partx sda
|
||||
None, None, # parted + partx sdb
|
||||
None, None # mdadms
|
||||
]
|
||||
|
||||
result = self.hardware.create_configuration(node, [])
|
||||
mocked_os_path_isdir.assert_has_calls([
|
||||
mock.call('/sys/firmware/efi')
|
||||
])
|
||||
mocked_execute.assert_has_calls([
|
||||
mock.call('parted', '/dev/sda', '-s', '--', 'mklabel', 'gpt'),
|
||||
mock.call('parted', '/dev/sdb', '-s', '--', 'mklabel', 'gpt'),
|
||||
mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--',
|
||||
'mkpart', 'primary', '8MiB', '10GiB'),
|
||||
mock.call('partx', '-u', '/dev/sda', check_exit_code=False),
|
||||
mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--',
|
||||
'mkpart', 'primary', '8MiB', '10GiB'),
|
||||
mock.call('partx', '-u', '/dev/sdb', check_exit_code=False),
|
||||
mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--',
|
||||
'mkpart', 'primary', '10GiB', '-1'),
|
||||
mock.call('partx', '-u', '/dev/sda', check_exit_code=False),
|
||||
mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--',
|
||||
'mkpart', 'primary', '10GiB', '-1'),
|
||||
mock.call('partx', '-u', '/dev/sdb', check_exit_code=False),
|
||||
mock.call('mdadm', '--create', '/dev/md0', '--force', '--run',
|
||||
'--metadata=1', '--level', '1', '--raid-devices', 2,
|
||||
'/dev/sda1', '/dev/sdb1'),
|
||||
mock.call('mdadm', '--create', '/dev/md1', '--force', '--run',
|
||||
'--metadata=1', '--level', '0', '--raid-devices', 2,
|
||||
'/dev/sda2', '/dev/sdb2')])
|
||||
self.assertEqual(raid_config, result)
|
||||
|
||||
@mock.patch.object(disk_utils, 'list_partitions', autospec=True,
|
||||
return_value=[])
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
@mock.patch.object(os.path, 'isdir', autospec=True, return_value=False)
|
||||
def test_create_configuration_no_max(self, _mocked_isdir, mocked_execute,
|
||||
mock_list_parts):
|
||||
node = self.node
|
||||
raid_config = {
|
||||
|
@ -3100,7 +3240,9 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||
@mock.patch.object(disk_utils, 'list_partitions', autospec=True,
|
||||
return_value=[])
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_create_configuration_max_is_first_logical(self, mocked_execute,
|
||||
@mock.patch.object(os.path, 'isdir', autospec=True, return_value=False)
|
||||
def test_create_configuration_max_is_first_logical(self, _mocked_isdir,
|
||||
mocked_execute,
|
||||
mock_list_parts):
|
||||
node = self.node
|
||||
raid_config = {
|
||||
|
@ -3247,7 +3389,10 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||
])
|
||||
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_create_configuration_invalid_raid_config(self, mocked_execute):
|
||||
@mock.patch.object(os.path, 'isdir', autospec=True, return_value=False)
|
||||
def test_create_configuration_invalid_raid_config(self,
|
||||
mocked_os_path_is_dir,
|
||||
mocked_execute):
|
||||
raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
|
@ -3323,8 +3468,12 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||
|
||||
@mock.patch.object(disk_utils, 'list_partitions', autospec=True)
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_create_configuration_partitions_detected(self, mocked_execute,
|
||||
@mock.patch.object(os.path, 'isdir', autospec=True, return_value=False)
|
||||
def test_create_configuration_partitions_detected(self,
|
||||
mocked_os_path_is_dir,
|
||||
mocked_execute,
|
||||
mock_list_parts):
|
||||
|
||||
raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
|
@ -3358,9 +3507,10 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||
@mock.patch.object(disk_utils, 'list_partitions', autospec=True,
|
||||
return_value=[])
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_create_configuration_device_handling_failures(self,
|
||||
mocked_execute,
|
||||
mock_list_parts):
|
||||
@mock.patch.object(os.path, 'isdir', autospec=True, return_value=False)
|
||||
def test_create_configuration_device_handling_failures(
|
||||
self, mocked_os_path_is_dir, mocked_execute, mock_list_parts):
|
||||
|
||||
raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
|
@ -3501,8 +3651,9 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||
@mock.patch.object(disk_utils, 'list_partitions', autospec=True,
|
||||
return_value=[])
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_create_configuration_with_nvme(self, mocked_execute,
|
||||
mock_list_parts):
|
||||
@mock.patch.object(os.path, 'isdir', autospec=True, return_value=True)
|
||||
def test_create_configuration_with_nvme(self, mocked_os_path_isdir,
|
||||
mocked_execute, mock_list_parts):
|
||||
raid_config = {
|
||||
"logical_disks": [
|
||||
{
|
||||
|
@ -3527,9 +3678,7 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||
|
||||
mocked_execute.side_effect = [
|
||||
None, # mklabel sda
|
||||
("WARNING MBR NOT GPT\n42", None), # sgdisk -F sda
|
||||
None, # mklabel sda
|
||||
("WARNING MBR NOT GPT\n42", None), # sgdisk -F sdb
|
||||
None, None, # parted + partx sda
|
||||
None, None, # parted + partx sdb
|
||||
None, None, # parted + partx sda
|
||||
|
@ -3541,16 +3690,14 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||
|
||||
mocked_execute.assert_has_calls([
|
||||
mock.call('parted', '/dev/nvme0n1', '-s', '--', 'mklabel',
|
||||
'msdos'),
|
||||
mock.call('sgdisk', '-F', '/dev/nvme0n1'),
|
||||
'gpt'),
|
||||
mock.call('parted', '/dev/nvme1n1', '-s', '--', 'mklabel',
|
||||
'msdos'),
|
||||
mock.call('sgdisk', '-F', '/dev/nvme1n1'),
|
||||
'gpt'),
|
||||
mock.call('parted', '/dev/nvme0n1', '-s', '-a', 'optimal', '--',
|
||||
'mkpart', 'primary', '42s', '10GiB'),
|
||||
'mkpart', 'primary', '129MiB', '10GiB'),
|
||||
mock.call('partx', '-u', '/dev/nvme0n1', check_exit_code=False),
|
||||
mock.call('parted', '/dev/nvme1n1', '-s', '-a', 'optimal', '--',
|
||||
'mkpart', 'primary', '42s', '10GiB'),
|
||||
'mkpart', 'primary', '129MiB', '10GiB'),
|
||||
mock.call('partx', '-u', '/dev/nvme1n1', check_exit_code=False),
|
||||
mock.call('parted', '/dev/nvme0n1', '-s', '-a', 'optimal', '--',
|
||||
'mkpart', 'primary', '10GiB', '-1'),
|
||||
|
@ -3569,7 +3716,10 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||
@mock.patch.object(disk_utils, 'list_partitions', autospec=True,
|
||||
return_value=[])
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_create_configuration_failure_with_nvme(self, mocked_execute,
|
||||
@mock.patch.object(os.path, 'isdir', autospec=True, return_value=True)
|
||||
def test_create_configuration_failure_with_nvme(self,
|
||||
mocked_os_path_isdir,
|
||||
mocked_execute,
|
||||
mock_list_parts):
|
||||
raid_config = {
|
||||
"logical_disks": [
|
||||
|
@ -3613,9 +3763,7 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||
error_regex = "Failed to create partitions on /dev/nvme0n1"
|
||||
mocked_execute.side_effect = [
|
||||
None, # partition tables on sda
|
||||
('42', None), # sgdisk -F sda
|
||||
None, # partition tables on sdb
|
||||
('42', None), # sgdisk -F sdb
|
||||
processutils.ProcessExecutionError]
|
||||
self.assertRaisesRegex(errors.SoftwareRAIDError, error_regex,
|
||||
self.hardware.create_configuration,
|
||||
|
@ -3625,9 +3773,7 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
|||
"on /dev/nvme0n1p1 /dev/nvme1n1p1")
|
||||
mocked_execute.side_effect = [
|
||||
None, # partition tables on sda
|
||||
('42', None), # sgdisk -F sda
|
||||
None, # partition tables on sdb
|
||||
('42', None), # sgdisk -F sdb
|
||||
None, None, None, None, # RAID-1 partitions on sd{a,b} + partx
|
||||
None, None, None, None, # RAID-N partitions on sd{a,b} + partx
|
||||
processutils.ProcessExecutionError]
|
||||
|
|
|
@ -644,7 +644,7 @@ class TestUtils(testtools.TestCase):
|
|||
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_get_efi_part_on_device_without_fs(self, mocked_execute):
|
||||
parted_ret = PARTED_OUTPUT_UNFORMATTED_NOFS.format('gpt')
|
||||
parted_ret = PARTED_OUTPUT_UNFORMATTED_NOFS
|
||||
mocked_execute.side_effect = [
|
||||
(parted_ret, None)
|
||||
]
|
||||
|
@ -664,6 +664,267 @@ class TestUtils(testtools.TestCase):
|
|||
[mock.call('parted', '-s', '/dev/sda', '--', 'print')]
|
||||
)
|
||||
|
||||
def test_extract_capability_from_dict(self):
|
||||
expected_dict = {"hello": "world"}
|
||||
root = {"capabilities": expected_dict}
|
||||
|
||||
self.assertDictEqual(
|
||||
expected_dict,
|
||||
utils.parse_capabilities(root))
|
||||
|
||||
def test_extract_capability_from_json_string(self):
|
||||
root = {'capabilities': '{"test": "world"}'}
|
||||
self.assertDictEqual(
|
||||
{"test": "world"},
|
||||
utils.parse_capabilities(root))
|
||||
|
||||
def test_extract_capability_from_old_format_caps(self):
|
||||
root = {'capabilities': 'test:world:2,hello:test1,badformat'}
|
||||
self.assertDictEqual(
|
||||
{'hello': 'test1'},
|
||||
utils.parse_capabilities(root))
|
||||
|
||||
@mock.patch.object(os.path, 'isdir', return_value=True, autospec=True)
|
||||
def test_boot_mode_fallback_uefi(self, mock_os):
|
||||
node = {}
|
||||
boot_mode = utils.get_node_boot_mode(node)
|
||||
self.assertEqual('uefi', boot_mode)
|
||||
mock_os.assert_called_once_with('/sys/firmware/efi')
|
||||
|
||||
@mock.patch.object(os.path, 'isdir', return_value=False, autospec=True)
|
||||
def test_boot_mode_fallback_bios(self, mock_os):
|
||||
node = {}
|
||||
boot_mode = utils.get_node_boot_mode(node)
|
||||
self.assertEqual('bios', boot_mode)
|
||||
mock_os.assert_called_once_with('/sys/firmware/efi')
|
||||
|
||||
@mock.patch.object(os.path, 'isdir', return_value=False, autospec=True)
|
||||
def test_boot_mode_from_driver_internal_info(self, mock_os):
|
||||
node = {
|
||||
'driver_internal_info': {
|
||||
'deploy_boot_mode': 'uefi'
|
||||
},
|
||||
}
|
||||
boot_mode = utils.get_node_boot_mode(node)
|
||||
self.assertEqual('uefi', boot_mode)
|
||||
mock_os.assert_called_once_with('/sys/firmware/efi')
|
||||
|
||||
@mock.patch.object(os.path, 'isdir', return_value=False, autospec=True)
|
||||
def test_boot_mode_from_properties_str(self, mock_os):
|
||||
node = {
|
||||
'driver_internal_info': {
|
||||
'deploy_boot_mode': 'bios'
|
||||
},
|
||||
'properties': {
|
||||
'capabilities': 'boot_mode:uefi'
|
||||
}
|
||||
}
|
||||
boot_mode = utils.get_node_boot_mode(node)
|
||||
self.assertEqual('uefi', boot_mode)
|
||||
mock_os.assert_called_once_with('/sys/firmware/efi')
|
||||
|
||||
@mock.patch.object(os.path, 'isdir', return_value=False, autospec=True)
|
||||
def test_boot_mode_from_properties_dict(self, mock_os):
|
||||
node = {
|
||||
'driver_internal_info': {
|
||||
'deploy_boot_mode': 'bios'
|
||||
},
|
||||
'properties': {
|
||||
'capabilities': {
|
||||
'boot_mode': 'uefi'
|
||||
}
|
||||
}
|
||||
}
|
||||
boot_mode = utils.get_node_boot_mode(node)
|
||||
self.assertEqual('uefi', boot_mode)
|
||||
mock_os.assert_called_once_with('/sys/firmware/efi')
|
||||
|
||||
@mock.patch.object(os.path, 'isdir', return_value=False, autospec=True)
|
||||
def test_boot_mode_from_properties_json_str(self, mock_os):
|
||||
node = {
|
||||
'driver_internal_info': {
|
||||
'deploy_boot_mode': 'bios'
|
||||
},
|
||||
'properties': {
|
||||
'capabilities': '{"boot_mode": "uefi"}'
|
||||
}
|
||||
}
|
||||
boot_mode = utils.get_node_boot_mode(node)
|
||||
self.assertEqual('uefi', boot_mode)
|
||||
mock_os.assert_called_once_with('/sys/firmware/efi')
|
||||
|
||||
@mock.patch.object(os.path, 'isdir', return_value=False, autospec=True)
|
||||
def test_boot_mode_override_with_instance_info(self, mock_os):
|
||||
node = {
|
||||
'driver_internal_info': {
|
||||
'deploy_boot_mode': 'bios'
|
||||
},
|
||||
'properties': {
|
||||
'capabilities': {
|
||||
'boot_mode': 'bios'
|
||||
}
|
||||
},
|
||||
'instance_info': {
|
||||
'deploy_boot_mode': 'uefi'
|
||||
}
|
||||
}
|
||||
|
||||
boot_mode = utils.get_node_boot_mode(node)
|
||||
self.assertEqual('uefi', boot_mode)
|
||||
mock_os.assert_called_once_with('/sys/firmware/efi')
|
||||
|
||||
@mock.patch.object(os.path, 'isdir', return_value=False, autospec=True)
|
||||
def test_boot_mode_implicit_with_secure_boot(self, mock_os):
|
||||
node = {
|
||||
'driver_internal_info': {
|
||||
'deploy_boot_mode': 'bios'
|
||||
},
|
||||
'properties': {
|
||||
'capabilities': {
|
||||
'boot_mode': 'bios',
|
||||
'secure_boot': 'TrUe'
|
||||
}
|
||||
},
|
||||
'instance_info': {
|
||||
'deploy_boot_mode': 'bios'
|
||||
}
|
||||
}
|
||||
|
||||
boot_mode = utils.get_node_boot_mode(node)
|
||||
self.assertEqual('uefi', boot_mode)
|
||||
mock_os.assert_has_calls([])
|
||||
|
||||
@mock.patch.object(os.path, 'isdir', return_value=False, autospec=True)
|
||||
def test_secure_boot_overriden_with_instance_info_caps(self, mock_os):
|
||||
node = {
|
||||
'driver_internal_info': {
|
||||
'deploy_boot_mode': 'bios'
|
||||
},
|
||||
'properties': {
|
||||
'capabilities': {
|
||||
'boot_mode': 'bios',
|
||||
'secure_boot': 'false'
|
||||
}
|
||||
},
|
||||
'instance_info': {
|
||||
'deploy_boot_mode': 'bios',
|
||||
'capabilities': {
|
||||
'secure_boot': 'true'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boot_mode = utils.get_node_boot_mode(node)
|
||||
self.assertEqual('uefi', boot_mode)
|
||||
mock_os.assert_has_calls([])
|
||||
|
||||
@mock.patch.object(os.path, 'isdir', return_value=True, autospec=True)
|
||||
def test_boot_mode_invalid_cap(self, mock_os):
|
||||
# In case of invalid boot mode specified we fallback to ramdisk boot
|
||||
# mode
|
||||
node = {
|
||||
'driver_internal_info': {
|
||||
'deploy_boot_mode': 'bios'
|
||||
},
|
||||
'properties': {
|
||||
'capabilities': {
|
||||
'boot_mode': 'sfkshfks'
|
||||
}
|
||||
}
|
||||
}
|
||||
boot_mode = utils.get_node_boot_mode(node)
|
||||
self.assertEqual('uefi', boot_mode)
|
||||
mock_os.assert_called_once_with('/sys/firmware/efi')
|
||||
|
||||
@mock.patch.object(utils, 'get_node_boot_mode', return_value='bios',
|
||||
autospec=True)
|
||||
def test_specified_partition_table_type(self, mock_boot_mode):
|
||||
node = {}
|
||||
label = utils.get_partition_table_type_from_specs(node)
|
||||
self.assertEqual('msdos', label)
|
||||
mock_boot_mode.assert_called_once_with(node)
|
||||
|
||||
@mock.patch.object(utils, 'get_node_boot_mode', return_value='uefi',
|
||||
autospec=True)
|
||||
def test_specified_partition_table_type_gpt(self, mock_boot_mode):
|
||||
node = {}
|
||||
label = utils.get_partition_table_type_from_specs(node)
|
||||
self.assertEqual('gpt', label)
|
||||
mock_boot_mode.assert_called_once_with(node)
|
||||
|
||||
@mock.patch.object(utils, 'get_node_boot_mode', return_value='bios',
|
||||
autospec=True)
|
||||
def test_specified_partition_table_type_with_disk_label(self,
|
||||
mock_boot_mode):
|
||||
node = {
|
||||
'properties': {
|
||||
'capabilities': 'disk_label:gpt'
|
||||
}
|
||||
}
|
||||
label = utils.get_partition_table_type_from_specs(node)
|
||||
self.assertEqual('gpt', label)
|
||||
mock_boot_mode.assert_has_calls([])
|
||||
|
||||
@mock.patch.object(utils, 'get_node_boot_mode', return_value='bios',
|
||||
autospec=True)
|
||||
def test_specified_partition_table_type_with_instance_disk_label(
|
||||
self, mock_boot_mode):
|
||||
# In case of invalid boot mode specified we fallback to ramdisk boot
|
||||
# mode
|
||||
node = {
|
||||
'instance_info': {
|
||||
'capabilities': 'disk_label:gpt'
|
||||
}
|
||||
}
|
||||
label = utils.get_partition_table_type_from_specs(node)
|
||||
self.assertEqual('gpt', label)
|
||||
mock_boot_mode.assert_has_calls([])
|
||||
|
||||
@mock.patch.object(utils, 'get_node_boot_mode', return_value='uefi',
|
||||
autospec=True)
|
||||
def test_specified_partition_table_type_disk_label_ignored_with_uefi(
|
||||
self, mock_boot_mode):
|
||||
# In case of invalid boot mode specified we fallback to ramdisk boot
|
||||
# mode
|
||||
node = {
|
||||
'instance_info': {
|
||||
'capabilities': 'disk_label:msdos'
|
||||
}
|
||||
}
|
||||
label = utils.get_partition_table_type_from_specs(node)
|
||||
self.assertEqual('gpt', label)
|
||||
mock_boot_mode.assert_has_calls([])
|
||||
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_scan_partition_table_type_gpt(self, mocked_execute):
|
||||
self._test_scan_partition_table_by_type(mocked_execute, 'gpt', 'gpt')
|
||||
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_scan_partition_table_type_msdos(self, mocked_execute):
|
||||
self._test_scan_partition_table_by_type(mocked_execute, 'msdos',
|
||||
'msdos')
|
||||
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
def test_scan_partition_table_type_unknown(self, mocked_execute):
|
||||
self._test_scan_partition_table_by_type(mocked_execute, 'whatever',
|
||||
'unknown')
|
||||
|
||||
def _test_scan_partition_table_by_type(self, mocked_execute,
|
||||
table_type_output,
|
||||
expected_table_type):
|
||||
|
||||
parted_ret = PARTED_OUTPUT_UNFORMATTED.format(table_type_output)
|
||||
|
||||
mocked_execute.side_effect = [
|
||||
(parted_ret, None),
|
||||
]
|
||||
|
||||
ret = utils.scan_partition_table_type('hello')
|
||||
mocked_execute.assert_has_calls(
|
||||
[mock.call('parted', '-s', 'hello', '--', 'print')]
|
||||
)
|
||||
self.assertEqual(expected_table_type, ret)
|
||||
|
||||
|
||||
class TestRemoveKeys(testtools.TestCase):
|
||||
def test_remove_keys(self):
|
||||
|
|
|
@ -30,6 +30,7 @@ from oslo_concurrency import processutils
|
|||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import base64
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import units
|
||||
|
||||
from ironic_python_agent import errors
|
||||
|
@ -70,6 +71,8 @@ COLLECT_LOGS_COMMANDS = {
|
|||
|
||||
DEVICE_EXTRACTOR = re.compile(r'^(?:(.*\d)p|(.*\D))(?:\d+)$')
|
||||
|
||||
PARTED_TABLE_TYPE_REGEX = re.compile(r'^.*partition\s+table\s*:\s*(gpt|msdos)',
|
||||
re.IGNORECASE)
|
||||
PARTED_ESP_PATTERN = re.compile(r'^\s*(\d+)\s.*\s\s.*\s.*esp(,|\s|$).*$')
|
||||
|
||||
|
||||
|
@ -454,6 +457,159 @@ def extract_device(part):
|
|||
return (m.group(1) or m.group(2))
|
||||
|
||||
|
||||
# See ironic.drivers.utils.get_node_capability
|
||||
def _parse_capabilities_str(cap_str):
|
||||
"""Extract capabilities from string.
|
||||
|
||||
:param cap_str: string meant to meet key1:value1,key2:value2 format
|
||||
:return: a dictionnary
|
||||
"""
|
||||
LOG.debug("Parsing capability string %s", cap_str)
|
||||
capabilities = {}
|
||||
|
||||
for node_capability in cap_str.split(','):
|
||||
parts = node_capability.split(':')
|
||||
if len(parts) == 2 and parts[0] and parts[1]:
|
||||
capabilities[parts[0]] = parts[1]
|
||||
else:
|
||||
LOG.warning("Ignoring malformed capability '%s'. "
|
||||
"Format should be 'key:val'.", node_capability)
|
||||
|
||||
LOG.debug("Parsed capabilities %s", capabilities)
|
||||
|
||||
return capabilities
|
||||
|
||||
|
||||
# See ironic.common.utils.parse_instance_info_capabilities. Same except that
|
||||
# we do not handle node.properties.capabilities and
|
||||
# node.instance_info.capabilities differently
|
||||
def parse_capabilities(root):
|
||||
"""Extract capabilities from provided root dictionary-behaving object.
|
||||
|
||||
root.get('capabilities', {}) value can either be a dict, or a json str, or
|
||||
a key1:value1,key2:value2 formatted string.
|
||||
|
||||
:param root: Anything behaving like a dict and containing capabilities
|
||||
formatted as expected. Can be node.get('properties', {}),
|
||||
node.get('instance_info', {}).
|
||||
:returns: A dictionary with the capabilities if found and well formatted,
|
||||
otherwise an empty dictionary.
|
||||
"""
|
||||
|
||||
capabilities = root.get('capabilities', {})
|
||||
if isinstance(capabilities, str):
|
||||
try:
|
||||
capabilities = jsonutils.loads(capabilities)
|
||||
except (ValueError, TypeError):
|
||||
capabilities = _parse_capabilities_str(capabilities)
|
||||
|
||||
if not isinstance(capabilities, dict):
|
||||
LOG.warning("Invalid capabilities %s", capabilities)
|
||||
return {}
|
||||
|
||||
return capabilities
|
||||
|
||||
|
||||
def _is_secure_boot(instance_info_caps, node_caps):
|
||||
"""Extract node secure boot property"""
|
||||
return 'true' == str(instance_info_caps.get(
|
||||
'secure_boot', node_caps.get('secure_boot', 'false'))).lower()
|
||||
|
||||
|
||||
# TODO(rg): This method should be mutualized with the one found in
|
||||
# ironic.drivers.modules.boot_mode_utils.
|
||||
# The only difference here:
|
||||
# 1. node is a dict, not an ironic.objects.node
|
||||
# 2. implicit bios boot mode when using trusted boot capability is removed:
|
||||
# there is no reason why trusted_boot should imply bios boot mode.
|
||||
def get_node_boot_mode(node):
|
||||
"""Returns the node boot mode.
|
||||
|
||||
It returns 'uefi' if 'secure_boot' is set to 'true' in
|
||||
'instance_info/capabilities' of node. Otherwise it directly look for boot
|
||||
mode hints into
|
||||
|
||||
:param node: dictionnary.
|
||||
:returns: 'bios' or 'uefi'
|
||||
"""
|
||||
instance_info = node.get('instance_info', {})
|
||||
instance_info_caps = parse_capabilities(instance_info)
|
||||
node_caps = parse_capabilities(node.get('properties', {}))
|
||||
|
||||
if _is_secure_boot(instance_info_caps, node_caps):
|
||||
LOG.debug('Deploy boot mode is implicitely uefi for because secure '
|
||||
'boot is activated.')
|
||||
return 'uefi'
|
||||
|
||||
ramdisk_boot_mode = 'uefi' if os.path.isdir('/sys/firmware/efi') \
|
||||
else 'bios'
|
||||
|
||||
# Priority order implemented in ironic
|
||||
boot_mode = instance_info.get(
|
||||
'deploy_boot_mode',
|
||||
node_caps.get(
|
||||
'boot_mode',
|
||||
node.get('driver_internal_info', {}).get('deploy_boot_mode',
|
||||
ramdisk_boot_mode))
|
||||
)
|
||||
|
||||
boot_mode = str(boot_mode).lower()
|
||||
if boot_mode not in ['uefi', 'bios']:
|
||||
boot_mode = ramdisk_boot_mode
|
||||
|
||||
LOG.debug('Deploy boot mode: %s', boot_mode)
|
||||
|
||||
return boot_mode
|
||||
|
||||
|
||||
def get_partition_table_type_from_specs(node):
|
||||
"""Returns the node partition label, gpt or msdos.
|
||||
|
||||
If boot mode is uefi, return gpt. Else, choice is open, look for
|
||||
disk_label capabilities (instance_info has priority over properties).
|
||||
|
||||
:param node:
|
||||
:return: gpt or msdos
|
||||
"""
|
||||
instance_info_caps = parse_capabilities(node.get('instance_info', {}))
|
||||
node_caps = parse_capabilities(node.get('properties', {}))
|
||||
|
||||
# Let's not make things more complicated than they already are.
|
||||
# We currently just ignore the specified disk label in case of uefi,
|
||||
# and force gpt, even if msdos is possible. Small amends needed if ever
|
||||
# needed (doubt that)
|
||||
|
||||
boot_mode = get_node_boot_mode(node)
|
||||
if boot_mode == 'uefi':
|
||||
return 'gpt'
|
||||
|
||||
disk_label = instance_info_caps.get(
|
||||
'disk_label',
|
||||
node_caps.get('disk_label', 'msdos')
|
||||
)
|
||||
return 'gpt' if disk_label == 'gpt' else 'msdos'
|
||||
|
||||
|
||||
def scan_partition_table_type(device):
|
||||
"""Get partition table type, msdos or gpt.
|
||||
|
||||
:param device_name: the name of the device
|
||||
:return: msdos, gpt or unknown
|
||||
"""
|
||||
out, _u = execute('parted', '-s', device, '--', 'print')
|
||||
out = out.splitlines()
|
||||
|
||||
for line in out:
|
||||
m = PARTED_TABLE_TYPE_REGEX.match(line)
|
||||
if m:
|
||||
return m.group(1)
|
||||
|
||||
LOG.warning("Unable to get partition table type for device %s.",
|
||||
device)
|
||||
|
||||
return 'unknown'
|
||||
|
||||
|
||||
def get_efi_part_on_device(device):
|
||||
"""Looks for the efi partition on a given device
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
features:
|
||||
|
|
||||
- Adds UEFI boot support for Software RAID, and for partition table
|
||||
creation based upon boot mode in use.
|
||||
upgrade:
|
||||
|
|
||||
- The type of the partition table created for Software RAID is now based
|
||||
upon the boot mode in use (GPT for UEFI or if explicitly passed via the
|
||||
instance's capabilities or the node's properties, otherwise MSDOS).
|
||||
- The amount of reserved space on the drives now also depends on the boot
|
||||
mode (128MiB for UEFI/GPT, 8MiB for BIOS/GPT, and one sector otherwise).
|
Loading…
Reference in New Issue