Fix software RAID creation on different physical devices
When creating multiple software RAID logical disks that use different sets of physical devices, the partition indices were incorrectly shared across all devices. This caused the second RAID array creation to fail because it tried to use partition indices that didn't exist on those specific devices. This change fixes the issue by tracking partition indices separately for each physical device, ensuring that each device's partitions are numbered correctly starting from their first available index. Closes-Bug: #2115211 Change-Id: I440db4654f3d1d54274d1eee8c4b21c2b0a18d22 Signed-off-by: Mohammed Naser <mnaser@vexxhost.com>
This commit is contained in:
Dmitry Tantsur
committed by
Mohammed Naser
parent
883e3cf057
commit
521811cbcc
5 changed files with 159 additions and 7 deletions
@@ -3057,9 +3057,11 @@ class GenericHardwareManager(HardwareManager):
parted_start_dict[device] = end
# Create the RAID devices.
# Create the RAID devices. The indices mapping tracks the last used
# partition index for each physical device.
indices = {}
for index, logical_disk in enumerate(logical_disks):
raid_utils.create_raid_device(index, logical_disk)
raid_utils.create_raid_device(index, logical_disk, indices)
LOG.info("Successfully created Software RAID")
@@ -3354,6 +3356,8 @@ class GenericHardwareManager(HardwareManager):
size2 = logical_disks[1]['size_gb']
# Only one logical disk is allowed to span the whole device.
# FIXME(dtantsur): this logic is not correct when logical disks use
# different physical devices.
if size1 == 'MAX' and size2 == 'MAX':
msg = ("Software RAID can have only one RAID device with "
"size 'MAX'")
@@ -202,24 +202,31 @@ def _get_actual_component_devices(raid_device):
return component_devices
def create_raid_device(index, logical_disk):
def create_raid_device(index, logical_disk, indices=None):
"""Create a raid device.
:param index: the index of the resulting md device.
:param logical_disk: the logical disk containing the devices used to
crete the raid.
:param indices: Mapping to track the last used partition index for each
physical device across calls to create_raid_device.
:raise: errors.SoftwareRAIDError if not able to create the raid device
or fails to re-add a device to a raid.
"""
md_device = '/dev/md%d' % index
component_devices = []
if indices is None:
# Backward compatibility with custom hardware managers
indices = {dev: index for dev in logical_disk['block_devices']}
for device in logical_disk['block_devices']:
# The partition delimiter for all common harddrives (sd[a-z]+)
part_delimiter = ''
if 'nvme' in device:
part_delimiter = 'p'
index_on_device = indices.get(device, 0)
component_devices.append(
device + part_delimiter + str(index + RAID_PARTITION))
device + part_delimiter + str(index_on_device + RAID_PARTITION))
indices[device] = index_on_device + 1
raid_level = logical_disk['raid_level']
# The schema check allows '1+0', but mdadm knows it as '10'.
if raid_level == '1+0':
@@ -4308,6 +4308,117 @@ class TestGenericHardwareManager(base.IronicAgentTest):
mock.call(x) for x in ['/dev/sda', '/dev/sdb']
])
@mock.patch.object(raid_utils, '_get_actual_component_devices',
autospec=True)
@mock.patch.object(utils, 'get_node_boot_mode', lambda node: 'bios')
@mock.patch.object(disk_utils, 'list_partitions', autospec=True,
return_value=[])
@mock.patch.object(utils, 'execute', autospec=True)
def test_create_configuration_with_different_disks(self, mocked_execute,
mock_list_parts,
mocked_actual_comp):
node = self.node
raid_config = {
"logical_disks": [
{
"size_gb": "10",
"raid_level": "1",
"controller": "software",
"physical_disks": [{'name': '/dev/sda'},
{'name': '/dev/sdb'}],
},
{
"size_gb": "MAX",
"raid_level": "0",
"controller": "software",
"physical_disks": [{'name': '/dev/sdc'},
{'name': '/dev/sdd'}],
},
]
}
node['target_raid_config'] = raid_config
device1 = hardware.BlockDevice('/dev/sda', 'sda', 107374182400, True)
device2 = hardware.BlockDevice('/dev/sdb', 'sdb', 107374182400, True)
device3 = hardware.BlockDevice('/dev/sdc', 'sdc', 107374182400, True)
device4 = hardware.BlockDevice('/dev/sdd', 'sdd', 107374182400, True)
self.hardware.list_block_devices = mock.Mock()
self.hardware.list_block_devices.return_value = [
device1,
device2,
device3,
device4,
]
mocked_execute.side_effect = [
None, # mklabel sda
('42', None), # sgdisk -F sda
None, # mklabel sda
('42', None), # sgdisk -F sdb
None, # mklabel sdc
('42', None), # sgdisk -F sdc
None, # mklabel sdd
('42', None), # sgdisk -F sdd
None, None, None, # parted + partx + udevadm_settle sda
None, None, None, # parted + partx + udevadm_settle sdb
None, None, None, # parted + partx + udevadm_settle sdc
None, None, None, # parted + partx + udevadm_settle sdd
None, None, None, # parted + partx + udevadm_settle sda
None, None, None, # parted + partx + udevadm_settle sdb
None, None, None, # parted + partx + udevadm_settle sdc
None, None, None, # parted + partx + udevadm_settle sdd
None, None # mdadms
]
mocked_actual_comp.side_effect = [
('/dev/sda1', '/dev/sdb1'),
('/dev/sdc1', '/dev/sdd1'),
]
result = self.hardware.create_configuration(node, [])
mocked_execute.assert_has_calls([
mock.call('parted', '/dev/sda', '-s', '--', 'mklabel', 'msdos'),
mock.call('sgdisk', '-F', '/dev/sda'),
mock.call('parted', '/dev/sdb', '-s', '--', 'mklabel', 'msdos'),
mock.call('sgdisk', '-F', '/dev/sdb'),
mock.call('parted', '/dev/sdc', '-s', '--', 'mklabel', 'msdos'),
mock.call('sgdisk', '-F', '/dev/sdc'),
mock.call('parted', '/dev/sdd', '-s', '--', 'mklabel', 'msdos'),
mock.call('sgdisk', '-F', '/dev/sdd'),
mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--',
'mkpart', 'primary', '42s', '10GiB'),
mock.call('partx', '-av', '/dev/sda', attempts=3,
delay_on_retry=True),
mock.call('udevadm', 'settle'),
mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--',
'mkpart', 'primary', '42s', '10GiB'),
mock.call('partx', '-av', '/dev/sdb', attempts=3,
delay_on_retry=True),
mock.call('udevadm', 'settle'),
mock.call('parted', '/dev/sdc', '-s', '-a', 'optimal', '--',
'mkpart', 'primary', '42s', '-1'),
mock.call('partx', '-av', '/dev/sdc', attempts=3,
delay_on_retry=True),
mock.call('udevadm', 'settle'),
mock.call('parted', '/dev/sdd', '-s', '-a', 'optimal', '--',
'mkpart', 'primary', '42s', '-1'),
mock.call('partx', '-av', '/dev/sdd', attempts=3,
delay_on_retry=True),
mock.call('udevadm', 'settle'),
mock.call('mdadm', '--create', '/dev/md0', '--force', '--run',
'--metadata=1', '--level', '1', '--name', 'md0',
'--raid-devices', 2, '/dev/sda1', '/dev/sdb1'),
mock.call('mdadm', '--create', '/dev/md1', '--force', '--run',
'--metadata=1', '--level', '0', '--name', 'md1',
'--raid-devices', 2, '/dev/sdc1', '/dev/sdd1')])
self.assertEqual(raid_config, result)
self.assertEqual(4, mock_list_parts.call_count)
mock_list_parts.assert_has_calls([
mock.call(x) for x in ['/dev/sda', '/dev/sdb',
'/dev/sdc', '/dev/sdd']
])
@mock.patch.object(utils, 'execute', autospec=True)
@mock.patch.object(os.path, 'isdir', autospec=True, return_value=False)
def test_create_configuration_invalid_raid_config(self,
@@ -52,7 +52,7 @@ class TestRaidUtils(base.IronicAgentTest):
'/dev/sdb1',
'/dev/sdc1']
raid_utils.create_raid_device(0, logical_disk)
raid_utils.create_raid_device(0, logical_disk, {})
mock_execute.assert_called_once_with(
'mdadm', '--create', '/dev/md0', '--force', '--run',
@@ -73,7 +73,7 @@ class TestRaidUtils(base.IronicAgentTest):
'/dev/sdb1',
'/dev/sdc1']
raid_utils.create_raid_device(0, logical_disk)
raid_utils.create_raid_device(0, logical_disk, {})
mock_execute.assert_called_once_with(
'mdadm', '--create', '/dev/md0', '--force', '--run',
@@ -92,7 +92,7 @@ class TestRaidUtils(base.IronicAgentTest):
mocked_components.return_value = ['/dev/sda1',
'/dev/sdc1']
raid_utils.create_raid_device(0, logical_disk)
raid_utils.create_raid_device(0, logical_disk, {})
expected_calls = [
mock.call('mdadm', '--create', '/dev/md0', '--force', '--run',
@@ -105,6 +105,29 @@ class TestRaidUtils(base.IronicAgentTest):
self.assertEqual(mock_execute.call_count, 2)
mock_execute.assert_has_calls(expected_calls)
@mock.patch.object(raid_utils, '_get_actual_component_devices',
autospec=True)
@mock.patch.object(utils, 'execute', autospec=True)
def test_create_raid_device_with_indices(self, mock_execute,
mocked_components):
indices = {'/dev/sda': 1, '/dev/sdb': 2}
logical_disk = {
"block_devices": ['/dev/sda', '/dev/sdb', '/dev/sdc'],
"raid_level": "1",
}
mocked_components.return_value = ['/dev/sda2',
'/dev/sdb3',
'/dev/sdc1']
raid_utils.create_raid_device(0, logical_disk, indices)
mock_execute.assert_called_once_with(
'mdadm', '--create', '/dev/md0', '--force', '--run',
'--metadata=1', '--level', '1', '--name', 'md0',
'--raid-devices', 3, '/dev/sda2', '/dev/sdb3', '/dev/sdc1')
self.assertEqual(
{'/dev/sda': 2, '/dev/sdb': 3, '/dev/sdc': 1}, indices)
@mock.patch.object(utils, 'execute', autospec=True)
def test_create_raid_device_fail_create_device(self, mock_execute):
logical_disk = {
@@ -0,0 +1,7 @@
---
fixes:
- |
Software RAID creation now correctly handles configurations where
logical disks use different sets of physical devices. Previously,
partition indices were incorrectly shared across all devices, causing
failures when creating multiple RAID arrays on different disks.
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.