Merge "Softraid: partitioning fixes"

This commit is contained in:
Zuul 2019-08-21 11:58:22 +00:00 committed by Gerrit Code Review
commit 0eff77ac8d
3 changed files with 268 additions and 39 deletions

@ -1479,6 +1479,7 @@ class GenericHardwareManager(HardwareManager):
raise errors.SoftwareRAIDError(msg)
# Create an MBR or GPT partition table on each disk.
parted_start_dict = {}
for block_device in block_devices:
LOG.info("Creating partition table on {}".format(
block_device.name))
@ -1490,21 +1491,71 @@ class GenericHardwareManager(HardwareManager):
block_device.name, e)
raise errors.SoftwareRAIDError(msg)
# Create the partitions which will become the component devices.
sector = '2048s'
out, _u = utils.execute('sgdisk', '-F', block_device.name)
# May differ from 2048s, according to device geometry (example:
# 4k disks).
parted_start_dict[block_device.name] = "%ss" % out.splitlines()[-1]
LOG.debug("First available sectors per devices %s", parted_start_dict)
# Reorder logical disks so that MAX comes last if any:
reordered_logical_disks = []
max_disk = None
for logical_disk in logical_disks:
psize = logical_disk['size_gb']
if psize == 'MAX':
psize = '-1'
max_disk = logical_disk
else:
psize = int(psize) * 1024
for device in block_devices:
reordered_logical_disks.append(logical_disk)
if max_disk:
reordered_logical_disks.append(max_disk)
logical_disks = reordered_logical_disks
# With the partitioning below, the first partition is not
# exactly the size_gb provided, but rather the size minus a small
# amount (often 2048*512B=1MiB, depending on the disk geometry).
# Easier to ignore. Another way could be to use sgdisk, which is really
# user-friendly to compute part boundaries automatically, instead of
# parted, then convert back to mbr table if needed and possible.
default_physical_disks = [device.name for device in block_devices]
for logical_disk in logical_disks:
# Note: from the doc,
# https://docs.openstack.org/ironic/latest/admin/raid.html#target-raid-configuration
# size_gb unit is GiB
psize = logical_disk['size_gb']
if psize == 'MAX':
psize = -1
else:
psize = int(psize)
for device in default_physical_disks:
start = parted_start_dict[device]
if isinstance(start, int):
start_str = '%dGiB' % start
else:
start_str = start
if psize == -1:
end_str = '-1'
end = '-1'
else:
if isinstance(start, int):
end = start + psize
else:
# First partition case, start is sth like 2048s
end = psize
end_str = '%dGiB' % end
try:
LOG.debug("Creating partition on {}: {} {}".format(
device.name, sector, psize))
utils.execute('parted', device.name, '-s', '-a',
device, start_str, end_str))
utils.execute('parted', device, '-s', '-a',
'optimal', '--', 'mkpart', 'primary',
sector, psize)
start_str, end_str)
# Necessary, if we want to avoid hitting
# an error when creating the mdadm array below
# 'mdadm: cannot open /dev/nvme1n1p1: No such file
@ -1512,26 +1563,27 @@ class GenericHardwareManager(HardwareManager):
# The real difference between partx and partprobe is
# unclear, but note that partprobe does not seem to
# work synchronously for nvme drives...
utils.execute("partx", "-u", device.name,
utils.execute("partx", "-u", device,
check_exit_code=False)
except processutils.ProcessExecutionError as e:
msg = "Failed to create partitions on {}: {}".format(
device.name, e)
device, e)
raise errors.SoftwareRAIDError(msg)
sector = psize
parted_start_dict[device] = end
# Create the RAID devices.
raid_device_count = len(block_devices)
for index, logical_disk in enumerate(logical_disks):
md_device = '/dev/md%d' % index
component_devices = []
for device in block_devices:
for device in default_physical_disks:
# The partition delimiter for all common harddrives (sd[a-z]+)
part_delimiter = ''
if 'nvme' in device.name:
if 'nvme' in device:
part_delimiter = 'p'
component_devices.append(
device.name + part_delimiter + str(index + 1))
device + part_delimiter + str(index + 1))
raid_level = logical_disk['raid_level']
# The schema check allows '1+0', but mdadm knows it as '10'.
if raid_level == '1+0':

@ -2735,7 +2735,7 @@ class TestGenericHardwareManager(base.IronicAgentTest):
raid_config = {
"logical_disks": [
{
"size_gb": "100",
"size_gb": "10",
"raid_level": "1",
"controller": "software",
},
@ -2747,29 +2747,43 @@ class TestGenericHardwareManager(base.IronicAgentTest):
]
}
node['target_raid_config'] = raid_config
device1 = hardware.BlockDevice('/dev/sda', 'sda', 1073741824, True)
device2 = hardware.BlockDevice('/dev/sdb', 'sdb', 1073741824, True)
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
('42', None), # sgdisk -F sda
None, # mklabel sda
('42', None), # sgdisk -F sdb
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_execute.assert_has_calls([
mock.call('parted', '/dev/sda', '-s', '--', 'mklabel',
expected_partition_table_type),
mock.call('sgdisk', '-F', '/dev/sda'),
mock.call('parted', '/dev/sdb', '-s', '--', 'mklabel',
expected_partition_table_type),
mock.call('sgdisk', '-F', '/dev/sdb'),
mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--',
'mkpart', 'primary', '2048s', 102400),
'mkpart', 'primary', '42s', '10GiB'),
mock.call('partx', '-u', '/dev/sda', check_exit_code=False),
mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--',
'mkpart', 'primary', '2048s', 102400),
'mkpart', 'primary', '42s', '10GiB'),
mock.call('partx', '-u', '/dev/sdb', check_exit_code=False),
mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--',
'mkpart', 'primary', 102400, '-1'),
'mkpart', 'primary', '10GiB', '-1'),
mock.call('partx', '-u', '/dev/sda', check_exit_code=False),
mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--',
'mkpart', 'primary', 102400, '-1'),
'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,
@ -2779,6 +2793,132 @@ class TestGenericHardwareManager(base.IronicAgentTest):
'/dev/sda2', '/dev/sdb2')])
self.assertEqual(raid_config, result)
@mock.patch.object(utils, 'execute', autospec=True)
def test_create_configuration_no_max(self, mocked_execute):
node = self.node
raid_config = {
"logical_disks": [
{
"size_gb": "10",
"raid_level": "1",
"controller": "software",
},
{
"size_gb": "20",
"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
('42', None), # sgdisk -F sda
None, # mklabel sda
('42', None), # sgdisk -F sdb
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_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/sda', '-s', '-a', 'optimal', '--',
'mkpart', 'primary', '42s', '10GiB'),
mock.call('partx', '-u', '/dev/sda', check_exit_code=False),
mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--',
'mkpart', 'primary', '42s', '10GiB'),
mock.call('partx', '-u', '/dev/sdb', check_exit_code=False),
mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--',
'mkpart', 'primary', '10GiB', '30GiB'),
mock.call('partx', '-u', '/dev/sda', check_exit_code=False),
mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--',
'mkpart', 'primary', '10GiB', '30GiB'),
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(utils, 'execute', autospec=True)
def test_create_configuration_max_is_first_logical(self, mocked_execute):
node = self.node
raid_config = {
"logical_disks": [
{
"size_gb": "MAX",
"raid_level": "1",
"controller": "software",
},
{
"size_gb": "20",
"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
('42', None), # sgdisk -F sda
None, # mklabel sda
('42', None), # sgdisk -F sdb
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_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/sda', '-s', '-a', 'optimal', '--',
'mkpart', 'primary', '42s', '20GiB'),
mock.call('partx', '-u', '/dev/sda', check_exit_code=False),
mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--',
'mkpart', 'primary', '42s', '20GiB'),
mock.call('partx', '-u', '/dev/sdb', check_exit_code=False),
mock.call('parted', '/dev/sda', '-s', '-a', 'optimal', '--',
'mkpart', 'primary', '20GiB', '-1'),
mock.call('partx', '-u', '/dev/sda', check_exit_code=False),
mock.call('parted', '/dev/sdb', '-s', '-a', 'optimal', '--',
'mkpart', 'primary', '20GiB', '-1'),
mock.call('partx', '-u', '/dev/sdb', check_exit_code=False),
mock.call('mdadm', '--create', '/dev/md0', '--force', '--run',
'--metadata=1', '--level', '0', '--raid-devices', 2,
'/dev/sda1', '/dev/sdb1'),
mock.call('mdadm', '--create', '/dev/md1', '--force', '--run',
'--metadata=1', '--level', '1', '--raid-devices', 2,
'/dev/sda2', '/dev/sdb2')])
self.assertEqual(raid_config, result)
@mock.patch.object(utils, 'execute', autospec=True)
def test_create_configuration_invalid_raid_config(self, mocked_execute):
raid_config = {
@ -2817,8 +2957,8 @@ class TestGenericHardwareManager(base.IronicAgentTest):
]
}
self.node['target_raid_config'] = raid_config
device1 = hardware.BlockDevice('/dev/sda', 'sda', 1073741824, True)
device2 = hardware.BlockDevice('/dev/sdb', 'sdb', 1073741824, True)
device1 = hardware.BlockDevice('/dev/sda', 'sda', 107374182400, True)
device2 = hardware.BlockDevice('/dev/sdb', 'sdb', 107374182400, True)
partition1 = hardware.BlockDevice('/dev/sdb1', 'sdb1', 268435456, True)
self.hardware.list_block_devices = mock.Mock()
self.hardware.list_block_devices.side_effect = [
@ -2846,8 +2986,8 @@ class TestGenericHardwareManager(base.IronicAgentTest):
]
}
self.node['target_raid_config'] = raid_config
device1 = hardware.BlockDevice('/dev/sda', 'sda', 1073741824, True)
device2 = hardware.BlockDevice('/dev/sdb', 'sdb', 1073741824, True)
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.side_effect = [
[device1, device2],
@ -2867,7 +3007,10 @@ class TestGenericHardwareManager(base.IronicAgentTest):
# partition creation
error_regex = "Failed to create partitions on /dev/sda"
mocked_execute.side_effect = [
None, None, # partition tables and partx on sd{a,b}
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,
@ -2876,7 +3019,10 @@ class TestGenericHardwareManager(base.IronicAgentTest):
error_regex = ("Failed to create md device /dev/md0 "
"on /dev/sda1 /dev/sdb1")
mocked_execute.side_effect = [
None, None, # partition tables on sd{a,b}
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]
@ -2929,7 +3075,7 @@ class TestGenericHardwareManager(base.IronicAgentTest):
raid_config = {
"logical_disks": [
{
"size_gb": "100",
"size_gb": "10",
"raid_level": "1",
"controller": "software",
},
@ -2942,30 +3088,44 @@ class TestGenericHardwareManager(base.IronicAgentTest):
}
self.node['target_raid_config'] = raid_config
device1 = hardware.BlockDevice('/dev/nvme0n1', 'nvme0n1',
1073741824, True)
107374182400, True)
device2 = hardware.BlockDevice('/dev/nvme1n1', 'nvme1n1',
1073741824, True)
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
("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
None, None, # parted + partx sdb
None, None # mdadms
]
result = self.hardware.create_configuration(self.node, [])
mocked_execute.assert_has_calls([
mock.call('parted', '/dev/nvme0n1', '-s', '--', 'mklabel',
'msdos'),
mock.call('sgdisk', '-F', '/dev/nvme0n1'),
mock.call('parted', '/dev/nvme1n1', '-s', '--', 'mklabel',
'msdos'),
mock.call('sgdisk', '-F', '/dev/nvme1n1'),
mock.call('parted', '/dev/nvme0n1', '-s', '-a', 'optimal', '--',
'mkpart', 'primary', '2048s', 102400),
'mkpart', 'primary', '42s', '10GiB'),
mock.call('partx', '-u', '/dev/nvme0n1', check_exit_code=False),
mock.call('parted', '/dev/nvme1n1', '-s', '-a', 'optimal', '--',
'mkpart', 'primary', '2048s', 102400),
'mkpart', 'primary', '42s', '10GiB'),
mock.call('partx', '-u', '/dev/nvme1n1', check_exit_code=False),
mock.call('parted', '/dev/nvme0n1', '-s', '-a', 'optimal', '--',
'mkpart', 'primary', 102400, '-1'),
'mkpart', 'primary', '10GiB', '-1'),
mock.call('partx', '-u', '/dev/nvme0n1', check_exit_code=False),
mock.call('parted', '/dev/nvme1n1', '-s', '-a', 'optimal', '--',
'mkpart', 'primary', 102400, '-1'),
'mkpart', 'primary', '10GiB', '-1'),
mock.call('partx', '-u', '/dev/nvme1n1', check_exit_code=False),
mock.call('mdadm', '--create', '/dev/md0', '--force', '--run',
'--metadata=1', '--level', '1', '--raid-devices', 2,
@ -2993,9 +3153,9 @@ class TestGenericHardwareManager(base.IronicAgentTest):
}
self.node['target_raid_config'] = raid_config
device1 = hardware.BlockDevice('/dev/nvme0n1', 'nvme0n1',
1073741824, True)
107374182400, True)
device2 = hardware.BlockDevice('/dev/nvme1n1', 'nvme1n1',
1073741824, True)
107374182400, True)
self.hardware.list_block_devices = mock.Mock()
self.hardware.list_block_devices.side_effect = [
[device1, device2],
@ -3015,7 +3175,10 @@ class TestGenericHardwareManager(base.IronicAgentTest):
# partition creation
error_regex = "Failed to create partitions on /dev/nvme0n1"
mocked_execute.side_effect = [
None, None, # partition tables and partx on sd{a,b}
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,
@ -3024,7 +3187,10 @@ class TestGenericHardwareManager(base.IronicAgentTest):
error_regex = ("Failed to create md device /dev/md0 "
"on /dev/nvme0n1p1 /dev/nvme1n1p1")
mocked_execute.side_effect = [
None, None, # partition tables on sd{a,b}
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]
@ -3081,7 +3247,7 @@ class TestGenericHardwareManager(base.IronicAgentTest):
def test_delete_configuration(self, mocked_execute, mocked_list,
mocked_get_component, mocked_get_holder):
raid_device1 = hardware.BlockDevice('/dev/md0', 'RAID-1',
1073741824, True)
107374182400, True)
raid_device2 = hardware.BlockDevice('/dev/md1', 'RAID-0',
2147483648, True)
hardware.list_all_block_devices.side_effect = [

@ -0,0 +1,11 @@
---
fixes:
- Fixes an issue with the psize conversion when creating software raid. From
the documentation,
https://docs.openstack.org/ironic/latest/admin/raid.html#target-raid-configuration,
size_gb unit is GiB but parted default unit is MB.
- Fixes a RAID creation issue when there are several logical drives, with
more than one having its size specified (i.e not 'MAX').
https://storyboard.openstack.org/#!/story/2006352
- Fixes a RAID creation issue when a logical drive with size 'MAX' is not
last in the list of logical drives.