Skip to content

Commit cfd3963

Browse files
authored
Merge pull request #56 from stackhpc/yoga_conditional_raid_esp
Conditional creation of RAIDed ESP for UEFI Software RAID
2 parents c02a094 + cc13d97 commit cfd3963

File tree

4 files changed

+126
-47
lines changed

4 files changed

+126
-47
lines changed

ironic_python_agent/raid_utils.py

Lines changed: 68 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import copy
1414
import re
15+
import shlex
1516

1617
from ironic_lib import disk_utils
1718
from ironic_lib import utils as il_utils
@@ -302,50 +303,58 @@ def prepare_boot_partitions_for_softraid(device, holders, efi_part,
302303
if efi_part:
303304
efi_part = '{}p{}'.format(device, efi_part['number'])
304305

305-
LOG.info("Creating EFI partitions on software RAID holder disks")
306-
# We know that we kept this space when configuring raid,see
307-
# hardware.GenericHardwareManager.create_configuration.
308-
# We could also directly get the EFI partition size.
309-
partsize_mib = ESP_SIZE_MIB
310-
partlabel_prefix = 'uefi-holder-'
311-
efi_partitions = []
312-
for number, holder in enumerate(holders):
313-
# NOTE: see utils.get_partition_table_type_from_specs
314-
# for uefi we know that we have setup a gpt partition table,
315-
# sgdisk can be used to edit table, more user friendly
316-
# for alignment and relative offsets
317-
partlabel = '{}{}'.format(partlabel_prefix, number)
318-
out, _u = utils.execute('sgdisk', '-F', holder)
319-
start_sector = '{}s'.format(out.splitlines()[-1].strip())
320-
out, _u = utils.execute(
321-
'sgdisk', '-n', '0:{}:+{}MiB'.format(start_sector,
322-
partsize_mib),
323-
'-t', '0:ef00', '-c', '0:{}'.format(partlabel), holder)
324-
325-
# Refresh part table
326-
utils.execute("partprobe")
327-
utils.execute("blkid")
328-
329-
target_part, _u = utils.execute(
330-
"blkid", "-l", "-t", "PARTLABEL={}".format(partlabel), holder)
331-
332-
target_part = target_part.splitlines()[-1].split(':', 1)[0]
333-
efi_partitions.append(target_part)
334-
335-
LOG.debug("EFI partition %s created on holder disk %s",
336-
target_part, holder)
337-
338-
# RAID the ESPs, metadata=1.0 is mandatory to be able to boot
339-
md_device = get_next_free_raid_device()
340-
LOG.debug("Creating md device %(md_device)s for the ESPs "
341-
"on %(efi_partitions)s",
342-
{'md_device': md_device, 'efi_partitions': efi_partitions})
343-
utils.execute('mdadm', '--create', md_device, '--force',
344-
'--run', '--metadata=1.0', '--level', '1',
345-
'--name', 'esp', '--raid-devices', len(efi_partitions),
346-
*efi_partitions)
347-
348-
disk_utils.trigger_device_rescan(md_device)
306+
# check if we have a RAIDed ESP already
307+
md_device = find_esp_raid()
308+
if md_device:
309+
LOG.info("Found RAIDed ESP %s, skip creation", md_device)
310+
else:
311+
LOG.info("Creating EFI partitions on software RAID holder disks")
312+
# We know that we kept this space when configuring raid,see
313+
# hardware.GenericHardwareManager.create_configuration.
314+
# We could also directly get the EFI partition size.
315+
partsize_mib = ESP_SIZE_MIB
316+
partlabel_prefix = 'uefi-holder-'
317+
efi_partitions = []
318+
for number, holder in enumerate(holders):
319+
# NOTE: see utils.get_partition_table_type_from_specs
320+
# for uefi we know that we have setup a gpt partition table,
321+
# sgdisk can be used to edit table, more user friendly
322+
# for alignment and relative offsets
323+
partlabel = '{}{}'.format(partlabel_prefix, number)
324+
out, _u = utils.execute('sgdisk', '-F', holder)
325+
start_sector = '{}s'.format(out.splitlines()[-1].strip())
326+
out, _u = utils.execute(
327+
'sgdisk', '-n', '0:{}:+{}MiB'.format(start_sector,
328+
partsize_mib),
329+
'-t', '0:ef00', '-c', '0:{}'.format(partlabel), holder)
330+
331+
# Refresh part table
332+
utils.execute("partprobe")
333+
utils.execute("blkid")
334+
335+
target_part, _u = utils.execute(
336+
"blkid", "-l", "-t", "PARTLABEL={}".format(partlabel),
337+
holder)
338+
339+
target_part = target_part.splitlines()[-1].split(':', 1)[0]
340+
efi_partitions.append(target_part)
341+
342+
LOG.debug("EFI partition %s created on holder disk %s",
343+
target_part, holder)
344+
345+
# RAID the ESPs, metadata=1.0 is mandatory to be able to boot
346+
md_device = get_next_free_raid_device()
347+
LOG.debug("Creating md device %(md_device)s for the ESPs "
348+
"on %(efi_partitions)s",
349+
{'md_device': md_device,
350+
'efi_partitions': efi_partitions})
351+
utils.execute('mdadm', '--create', md_device, '--force',
352+
'--run', '--metadata=1.0', '--level', '1',
353+
'--name', 'esp', '--raid-devices',
354+
len(efi_partitions),
355+
*efi_partitions)
356+
357+
disk_utils.trigger_device_rescan(md_device)
349358

350359
if efi_part:
351360
# Blockdev copy the source ESP and erase it
@@ -380,3 +389,18 @@ def prepare_boot_partitions_for_softraid(device, holders, efi_part,
380389
# disk, as in virtual disk, where to load the data from.
381390
# Since there is a structural difference, this means it will
382391
# fail.
392+
393+
394+
def find_esp_raid():
395+
"""Find the ESP md device in case of a rebuild."""
396+
397+
# find devices of type 'RAID1' and fstype 'VFAT'
398+
lsblk = utils.execute('lsblk', '-PbioNAME,TYPE,FSTYPE')
399+
report = lsblk[0]
400+
for line in report.split('\n'):
401+
dev = {}
402+
vals = shlex.split(line)
403+
for key, val in (v.split('=', 1) for v in vals):
404+
dev[key] = val.strip()
405+
if dev.get('TYPE') == 'raid1' and dev.get('FSTYPE') == 'vfat':
406+
return '/dev/' + dev.get('NAME')

ironic_python_agent/tests/unit/samples/hardware_samples.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,3 +1593,33 @@
15931593
' `-+- policy=\'service-time 0\' prio=1 status=active\n'
15941594
' `- 0:0:0:0 device s 8:0 active ready running\n'
15951595
)
1596+
1597+
LSBLK_OUPUT = ("""
1598+
NAME="sda" TYPE="disk" FSTYPE=""
1599+
NAME="sdb" TYPE="disk" FSTYPE=""
1600+
""")
1601+
1602+
LSBLK_OUPUT_ESP_RAID = ("""
1603+
NAME="sda" TYPE="disk" FSTYPE=""
1604+
NAME="sda1" TYPE="part" FSTYPE="linux_raid_member"
1605+
NAME="md127" TYPE="raid1" FSTYPE=""
1606+
NAME="md127p1" TYPE="md" FSTYPE="xfs"
1607+
NAME="md127p2" TYPE="md" FSTYPE="iso9660"
1608+
NAME="md127p14" TYPE="md" FSTYPE=""
1609+
NAME="md127p15" TYPE="md" FSTYPE=""
1610+
NAME="sda2" TYPE="part" FSTYPE="linux_raid_member"
1611+
NAME="md126" TYPE="raid0" FSTYPE=""
1612+
NAME="sda3" TYPE="part" FSTYPE="linux_raid_member"
1613+
NAME="md125" TYPE="raid1" FSTYPE="vfat"
1614+
NAME="sdb" TYPE="disk" FSTYPE=""
1615+
NAME="sdb1" TYPE="part" FSTYPE="linux_raid_member"
1616+
NAME="md127" TYPE="raid1" FSTYPE=""
1617+
NAME="md127p1" TYPE="md" FSTYPE="xfs"
1618+
NAME="md127p2" TYPE="md" FSTYPE="iso9660"
1619+
NAME="md127p14" TYPE="md" FSTYPE=""
1620+
NAME="md127p15" TYPE="md" FSTYPE=""
1621+
NAME="sdb2" TYPE="part" FSTYPE="linux_raid_member"
1622+
NAME="md126" TYPE="raid0" FSTYPE=""
1623+
NAME="sdb3" TYPE="part" FSTYPE="linux_raid_member"
1624+
NAME="md125" TYPE="raid1" FSTYPE="vfat"
1625+
""")

ironic_python_agent/tests/unit/test_raid_utils.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ def test_create_raid_device_fail_read_device(self, mock_execute,
117117
raid_utils.create_raid_device, 0,
118118
logical_disk)
119119

120+
@mock.patch.object(raid_utils, 'find_esp_raid', autospec=True)
120121
@mock.patch.object(disk_utils, 'trigger_device_rescan', autospec=True)
121122
@mock.patch.object(raid_utils, 'get_next_free_raid_device', autospec=True,
122123
return_value='/dev/md42')
@@ -125,7 +126,7 @@ def test_create_raid_device_fail_read_device(self, mock_execute,
125126
@mock.patch.object(disk_utils, 'find_efi_partition', autospec=True)
126127
def test_prepare_boot_partitions_for_softraid_uefi_gpt(
127128
self, mock_efi_part, mock_execute, mock_dispatch,
128-
mock_free_raid_device, mock_rescan):
129+
mock_free_raid_device, mock_rescan, mock_find_esp):
129130
mock_efi_part.return_value = {'number': '12'}
130131
mock_execute.side_effect = [
131132
('451', None), # sgdisk -F
@@ -142,6 +143,7 @@ def test_prepare_boot_partitions_for_softraid_uefi_gpt(
142143
(None, None), # cp
143144
(None, None), # wipefs
144145
]
146+
mock_find_esp.return_value = None
145147

146148
efi_part = raid_utils.prepare_boot_partitions_for_softraid(
147149
'/dev/md0', ['/dev/sda', '/dev/sdb'], None,
@@ -173,6 +175,7 @@ def test_prepare_boot_partitions_for_softraid_uefi_gpt(
173175
self.assertEqual(efi_part, '/dev/md42')
174176
mock_rescan.assert_called_once_with('/dev/md42')
175177

178+
@mock.patch.object(raid_utils, 'find_esp_raid', autospec=True)
176179
@mock.patch.object(disk_utils, 'trigger_device_rescan', autospec=True)
177180
@mock.patch.object(raid_utils, 'get_next_free_raid_device', autospec=True,
178181
return_value='/dev/md42')
@@ -182,7 +185,7 @@ def test_prepare_boot_partitions_for_softraid_uefi_gpt(
182185
@mock.patch.object(ilib_utils, 'mkfs', autospec=True)
183186
def test_prepare_boot_partitions_for_softraid_uefi_gpt_esp_not_found(
184187
self, mock_mkfs, mock_efi_part, mock_execute, mock_dispatch,
185-
mock_free_raid_device, mock_rescan):
188+
mock_free_raid_device, mock_rescan, mock_find_esp):
186189
mock_efi_part.return_value = None
187190
mock_execute.side_effect = [
188191
('451', None), # sgdisk -F
@@ -197,6 +200,7 @@ def test_prepare_boot_partitions_for_softraid_uefi_gpt_esp_not_found(
197200
('/dev/sdb14: whatever', None), # blkid
198201
(None, None), # mdadm
199202
]
203+
mock_find_esp.return_value = None
200204

201205
efi_part = raid_utils.prepare_boot_partitions_for_softraid(
202206
'/dev/md0', ['/dev/sda', '/dev/sdb'], None,
@@ -226,14 +230,15 @@ def test_prepare_boot_partitions_for_softraid_uefi_gpt_esp_not_found(
226230
self.assertEqual(efi_part, '/dev/md42')
227231
mock_rescan.assert_called_once_with('/dev/md42')
228232

233+
@mock.patch.object(raid_utils, 'find_esp_raid', autospec=True)
229234
@mock.patch.object(disk_utils, 'trigger_device_rescan', autospec=True)
230235
@mock.patch.object(raid_utils, 'get_next_free_raid_device', autospec=True,
231236
return_value='/dev/md42')
232237
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
233238
@mock.patch.object(ilib_utils, 'execute', autospec=True)
234239
def test_prepare_boot_partitions_for_softraid_uefi_gpt_efi_provided(
235240
self, mock_execute, mock_dispatch, mock_free_raid_device,
236-
mock_rescan):
241+
mock_rescan, mock_find_esp):
237242
mock_execute.side_effect = [
238243
('451', None), # sgdisk -F
239244
(None, None), # sgdisk create part
@@ -249,6 +254,7 @@ def test_prepare_boot_partitions_for_softraid_uefi_gpt_efi_provided(
249254
(None, None), # cp
250255
(None, None), # wipefs
251256
]
257+
mock_find_esp.return_value = None
252258

253259
efi_part = raid_utils.prepare_boot_partitions_for_softraid(
254260
'/dev/md0', ['/dev/sda', '/dev/sdb'], '/dev/md0p15',
@@ -353,3 +359,17 @@ def test_no_device(self, mock_dispatch):
353359
]
354360
self.assertRaises(errors.SoftwareRAIDError,
355361
raid_utils.get_next_free_raid_device)
362+
363+
364+
@mock.patch.object(utils, 'execute', autospec=True)
365+
class TestFindESPRAID(base.IronicAgentTest):
366+
367+
def test_no_esp_raid(self, mock_execute):
368+
mock_execute.side_effect = [(hws.LSBLK_OUPUT, '')]
369+
result = raid_utils.find_esp_raid()
370+
self.assertIsNone(result)
371+
372+
def test_esp_raid(self, mock_execute):
373+
mock_execute.side_effect = [(hws.LSBLK_OUPUT_ESP_RAID, '')]
374+
result = raid_utils.find_esp_raid()
375+
self.assertEqual('/dev/md125', result)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
fixes:
3+
- |
4+
Fixes an issue with rebuilding instances on Software RAID with
5+
RAIDed ESP partitions.

0 commit comments

Comments
 (0)