Use canonical device name for RAID device for ESP

It seems like tinyIPA silently replaces /dev/md/esp with /dev/md127.
Find the next free /dev/md device and use it instead.

Also rescan the resulting device before copying files.

Change-Id: Ie04f530be434c4b1561e75f387b9da679e4607e0
Depends-On: https://review.opendev.org/c/openstack/ironic/+/827129/
This commit is contained in:
Dmitry Tantsur 2022-01-31 11:19:10 +01:00
parent 62c5674a60
commit 6ebf041704
4 changed files with 71 additions and 17 deletions

View File

@ -175,15 +175,17 @@ def prepare_boot_partitions_for_softraid(device, holders, efi_part,
target_part, holder) target_part, holder)
# RAID the ESPs, metadata=1.0 is mandatory to be able to boot # RAID the ESPs, metadata=1.0 is mandatory to be able to boot
md_device = '/dev/md/esp' md_device = raid_utils.get_next_free_raid_device()
LOG.debug("Creating md device %(md_device)s for the ESPs " LOG.debug("Creating md device %(md_device)s for the ESPs "
"on %(efi_partitions)s", "on %(efi_partitions)s",
{'md_device': md_device, 'efi_partitions': efi_partitions}) {'md_device': md_device, 'efi_partitions': efi_partitions})
utils.execute('mdadm', '--create', md_device, '--force', utils.execute('mdadm', '--create', md_device, '--force',
'--run', '--metadata=1.0', '--level', '1', '--run', '--metadata=1.0', '--level', '1',
'--raid-devices', len(efi_partitions), '--name', 'esp', '--raid-devices', len(efi_partitions),
*efi_partitions) *efi_partitions)
disk_utils.trigger_device_rescan(md_device)
if efi_part: if efi_part:
# Blockdev copy the source ESP and erase it # Blockdev copy the source ESP and erase it
LOG.debug("Relocating EFI %s to %s", efi_part, md_device) LOG.debug("Relocating EFI %s to %s", efi_part, md_device)

View File

@ -248,3 +248,17 @@ def create_raid_device(index, logical_disk):
msg = "Failed re-add {} to {}: {}".format( msg = "Failed re-add {} to {}: {}".format(
dev, md_device, e) dev, md_device, e)
raise errors.SoftwareRAIDError(msg) raise errors.SoftwareRAIDError(msg)
def get_next_free_raid_device():
"""Get a device name that is still free."""
from ironic_python_agent import hardware
names = {dev.name for dev in
hardware.dispatch_to_managers('list_block_devices')}
for idx in range(128):
name = f'/dev/md{idx}'
if name not in names:
return name
raise errors.SoftwareRAIDError("No free md (RAID) devices are left")

View File

@ -27,6 +27,7 @@ from ironic_python_agent import errors
from ironic_python_agent.extensions import image from ironic_python_agent.extensions import image
from ironic_python_agent import hardware from ironic_python_agent import hardware
from ironic_python_agent import partition_utils from ironic_python_agent import partition_utils
from ironic_python_agent import raid_utils
from ironic_python_agent.tests.unit import base from ironic_python_agent.tests.unit import base
@ -1653,9 +1654,13 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640
uuid=self.fake_root_uuid) uuid=self.fake_root_uuid)
self.assertFalse(mock_dispatch.called) self.assertFalse(mock_dispatch.called)
@mock.patch.object(disk_utils, 'trigger_device_rescan', autospec=True)
@mock.patch.object(raid_utils, 'get_next_free_raid_device', autospec=True,
return_value='/dev/md42')
@mock.patch.object(disk_utils, 'find_efi_partition', autospec=True) @mock.patch.object(disk_utils, 'find_efi_partition', autospec=True)
def test_prepare_boot_partitions_for_softraid_uefi_gpt( def test_prepare_boot_partitions_for_softraid_uefi_gpt(
self, mock_efi_part, mock_execute, mock_dispatch): self, mock_efi_part, mock_free_raid_device, mock_rescan,
mock_execute, mock_dispatch):
mock_efi_part.return_value = {'number': '12'} mock_efi_part.return_value = {'number': '12'}
mock_execute.side_effect = [ mock_execute.side_effect = [
('451', None), # sgdisk -F ('451', None), # sgdisk -F
@ -1693,19 +1698,24 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640
mock.call('blkid'), mock.call('blkid'),
mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-1', mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-1',
'/dev/sdb'), '/dev/sdb'),
mock.call('mdadm', '--create', '/dev/md/esp', '--force', '--run', mock.call('mdadm', '--create', '/dev/md42', '--force', '--run',
'--metadata=1.0', '--level', '1', '--raid-devices', 2, '--metadata=1.0', '--level', '1', '--name', 'esp',
'/dev/sda12', '/dev/sdb14'), '--raid-devices', 2, '/dev/sda12', '/dev/sdb14'),
mock.call('cp', '/dev/md0p12', '/dev/md/esp'), mock.call('cp', '/dev/md0p12', '/dev/md42'),
mock.call('wipefs', '-a', '/dev/md0p12') mock.call('wipefs', '-a', '/dev/md0p12')
] ]
mock_execute.assert_has_calls(expected, any_order=False) mock_execute.assert_has_calls(expected, any_order=False)
self.assertEqual(efi_part, '/dev/md/esp') self.assertEqual(efi_part, '/dev/md42')
mock_rescan.assert_called_once_with('/dev/md42')
@mock.patch.object(disk_utils, 'trigger_device_rescan', autospec=True)
@mock.patch.object(raid_utils, 'get_next_free_raid_device', autospec=True,
return_value='/dev/md42')
@mock.patch.object(disk_utils, 'find_efi_partition', autospec=True) @mock.patch.object(disk_utils, 'find_efi_partition', autospec=True)
@mock.patch.object(ilib_utils, 'mkfs', autospec=True) @mock.patch.object(ilib_utils, 'mkfs', autospec=True)
def test_prepare_boot_partitions_for_softraid_uefi_gpt_esp_not_found( def test_prepare_boot_partitions_for_softraid_uefi_gpt_esp_not_found(
self, mock_mkfs, mock_efi_part, mock_execute, mock_dispatch): self, mock_mkfs, mock_efi_part, mock_free_raid_device,
mock_rescan, mock_execute, mock_dispatch):
mock_efi_part.return_value = None mock_efi_part.return_value = None
mock_execute.side_effect = [ mock_execute.side_effect = [
('451', None), # sgdisk -F ('451', None), # sgdisk -F
@ -1744,12 +1754,17 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640
] ]
mock_execute.assert_has_calls(expected, any_order=False) mock_execute.assert_has_calls(expected, any_order=False)
mock_mkfs.assert_has_calls([ mock_mkfs.assert_has_calls([
mock.call(path='/dev/md/esp', label='efi-part', fs='vfat'), mock.call(path='/dev/md42', label='efi-part', fs='vfat'),
], any_order=False) ], any_order=False)
self.assertEqual(efi_part, '/dev/md/esp') self.assertEqual(efi_part, '/dev/md42')
mock_rescan.assert_called_once_with('/dev/md42')
@mock.patch.object(disk_utils, 'trigger_device_rescan', autospec=True)
@mock.patch.object(raid_utils, 'get_next_free_raid_device', autospec=True,
return_value='/dev/md42')
def test_prepare_boot_partitions_for_softraid_uefi_gpt_efi_provided( def test_prepare_boot_partitions_for_softraid_uefi_gpt_efi_provided(
self, mock_execute, mock_dispatch): self, mock_free_raid_device, mock_rescan,
mock_execute, mock_dispatch):
mock_execute.side_effect = [ mock_execute.side_effect = [
('451', None), # sgdisk -F ('451', None), # sgdisk -F
(None, None), # sgdisk create part (None, None), # sgdisk create part
@ -1785,14 +1800,14 @@ Boot0004* ironic1 HD(1,GPT,55db8d03-c8f6-4a5b-9155-790dddc348fa,0x800,0x640
mock.call('blkid'), mock.call('blkid'),
mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-1', mock.call('blkid', '-l', '-t', 'PARTLABEL=uefi-holder-1',
'/dev/sdb'), '/dev/sdb'),
mock.call('mdadm', '--create', '/dev/md/esp', '--force', '--run', mock.call('mdadm', '--create', '/dev/md42', '--force', '--run',
'--metadata=1.0', '--level', '1', '--raid-devices', 2, '--metadata=1.0', '--level', '1', '--name', 'esp',
'/dev/sda12', '/dev/sdb14'), '--raid-devices', 2, '/dev/sda12', '/dev/sdb14'),
mock.call('cp', '/dev/md0p15', '/dev/md/esp'), mock.call('cp', '/dev/md0p15', '/dev/md42'),
mock.call('wipefs', '-a', '/dev/md0p15') mock.call('wipefs', '-a', '/dev/md0p15')
] ]
mock_execute.assert_has_calls(expected, any_order=False) mock_execute.assert_has_calls(expected, any_order=False)
self.assertEqual(efi_part, '/dev/md/esp') self.assertEqual(efi_part, '/dev/md42')
@mock.patch.object(disk_utils, 'get_partition_table_type', autospec=True, @mock.patch.object(disk_utils, 'get_partition_table_type', autospec=True,
return_value='msdos') return_value='msdos')

View File

@ -15,9 +15,11 @@ from unittest import mock
from oslo_concurrency import processutils from oslo_concurrency import processutils
from ironic_python_agent import errors from ironic_python_agent import errors
from ironic_python_agent import hardware
from ironic_python_agent import raid_utils from ironic_python_agent import raid_utils
from ironic_python_agent.tests.unit import base from ironic_python_agent.tests.unit import base
from ironic_python_agent.tests.unit.samples import hardware_samples as hws from ironic_python_agent.tests.unit.samples import hardware_samples as hws
from ironic_python_agent.tests.unit import test_hardware
from ironic_python_agent import utils from ironic_python_agent import utils
@ -112,3 +114,24 @@ class TestRaidUtils(base.IronicAgentTest):
"Failed re-add /dev/sdb1 to /dev/md0", "Failed re-add /dev/sdb1 to /dev/md0",
raid_utils.create_raid_device, 0, raid_utils.create_raid_device, 0,
logical_disk) logical_disk)
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
class TestGetNextFreeRaidDevice(base.IronicAgentTest):
def test_ok(self, mock_dispatch):
mock_dispatch.return_value = \
test_hardware.RAID_BLK_DEVICE_TEMPLATE_DEVICES
result = raid_utils.get_next_free_raid_device()
self.assertEqual('/dev/md2', result)
mock_dispatch.assert_called_once_with('list_block_devices')
def test_no_device(self, mock_dispatch):
mock_dispatch.return_value = [
hardware.BlockDevice(name=f'/dev/md{idx}', model='RAID',
size=1765517033470, rotational=False,
vendor="FooTastic", uuid="")
for idx in range(128)
]
self.assertRaises(errors.SoftwareRAIDError,
raid_utils.get_next_free_raid_device)