Set rd.md.uuid kernel parameter when deploying on software raid

When deploying an image to a software raid array, it is currently
required that the deployed image assembles the md arrays automatically
so that the rootfs can be mounted. In order to remove this
requirement/limitation on the deployed image we can add rd.md.uuid to
the kernel command line with the raid array's uuid.

Story: 2006648
Task: 36884
Change-Id: I42cb198753ecd84b7eaef6b5aa7c2064535bfe0e
(cherry picked from commit 1975478097)
This commit is contained in:
Andrei Nistor 2019-10-17 11:14:04 +00:00 committed by Iury Gregory Melo Ferreira
parent 9681fd712d
commit 72b85c539d
3 changed files with 74 additions and 15 deletions

View File

@ -14,6 +14,7 @@
# under the License. # under the License.
import os import os
import re
import shlex import shlex
import shutil import shutil
import stat import stat
@ -123,6 +124,16 @@ def _get_partition(device, uuid):
raise errors.CommandExecutionError(error_msg) raise errors.CommandExecutionError(error_msg)
def _has_dracut(root):
try:
utils.execute('chroot %(path)s /bin/sh -c '
'"which dracut"' %
{'path': root}, shell=True)
except processutils.ProcessExecutionError:
return False
return True
def _install_grub2(device, root_uuid, efi_system_part_uuid=None, def _install_grub2(device, root_uuid, efi_system_part_uuid=None,
prep_boot_part_uuid=None): prep_boot_part_uuid=None):
"""Install GRUB2 bootloader on a given device.""" """Install GRUB2 bootloader on a given device."""
@ -202,6 +213,22 @@ def _install_grub2(device, root_uuid, efi_system_part_uuid=None,
{'path': path, 'bin': binary_name, 'dev': device}, {'path': path, 'bin': binary_name, 'dev': device},
shell=True, env_variables={'PATH': path_variable}) shell=True, env_variables={'PATH': path_variable})
# If the image has dracut installed, set the rd.md.uuid kernel
# parameter for discovered md devices.
if hardware.is_md_device(device) and _has_dracut(path):
rd_md_uuids = ["rd.md.uuid=%s" % x['UUID']
for x in hardware.md_get_raid_devices().values()]
LOG.debug("Setting rd.md.uuid kernel parameters: %s", rd_md_uuids)
with open('%s/etc/default/grub' % path, 'r') as g:
contents = g.read()
with open('%s/etc/default/grub' % path, 'w') as g:
g.write(
re.sub(r'GRUB_CMDLINE_LINUX="(.*)"',
r'GRUB_CMDLINE_LINUX="\1 %s"'
% " ".join(rd_md_uuids),
contents))
# Generate the grub configuration file # Generate the grub configuration file
utils.execute('chroot %(path)s /bin/sh -c ' utils.execute('chroot %(path)s /bin/sh -c '
'"%(bin)s-mkconfig -o ' '"%(bin)s-mkconfig -o '

View File

@ -224,6 +224,24 @@ def md_restart(raid_device):
raise errors.CommandExecutionError(error_msg) raise errors.CommandExecutionError(error_msg)
def md_get_raid_devices():
"""Get all discovered Software RAID (md) devices
:return: A python dict containing details about the discovered RAID
devices
"""
report = utils.execute('mdadm', '--examine', '--scan')[0]
lines = report.splitlines()
result = {}
for line in lines:
vals = shlex.split(line)
device = vals[1]
result[device] = {}
for key, val in (v.split('=', 1) for v in vals[2:]):
result[device][key] = val.strip()
return result
def _md_scan_and_assemble(): def _md_scan_and_assemble():
"""Scan all md devices and assemble RAID arrays from them. """Scan all md devices and assemble RAID arrays from them.

View File

@ -92,14 +92,16 @@ class TestImageExtension(base.IronicAgentTest):
mock_iscsi_clean.assert_called_once_with(self.fake_dev) mock_iscsi_clean.assert_called_once_with(self.fake_dev)
@mock.patch.object(hardware, 'is_md_device', autospec=True) @mock.patch.object(hardware, 'is_md_device', autospec=True)
@mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
@mock.patch.object(os, 'environ', autospec=True) @mock.patch.object(os, 'environ', autospec=True)
@mock.patch.object(image, '_get_partition', autospec=True) @mock.patch.object(image, '_get_partition', autospec=True)
def test__install_grub2(self, mock_get_part_uuid, environ_mock, def test__install_grub2(self, mock_get_part_uuid, environ_mock,
mock_is_md_device, mock_execute, mock_md_get_raid_devices, mock_is_md_device,
mock_dispatch): mock_execute, mock_dispatch):
mock_get_part_uuid.return_value = self.fake_root_part mock_get_part_uuid.return_value = self.fake_root_part
environ_mock.get.return_value = '/sbin' environ_mock.get.return_value = '/sbin'
mock_is_md_device.side_effect = [False] mock_is_md_device.side_effect = [False, False]
mock_md_get_raid_devices.return_value = {}
image._install_grub2(self.fake_dev, self.fake_root_uuid) image._install_grub2(self.fake_dev, self.fake_root_uuid)
expected = [mock.call('mount', '/dev/fake2', self.fake_dir), expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
@ -136,15 +138,18 @@ class TestImageExtension(base.IronicAgentTest):
self.assertFalse(mock_dispatch.called) self.assertFalse(mock_dispatch.called)
@mock.patch.object(hardware, 'is_md_device', autospec=True) @mock.patch.object(hardware, 'is_md_device', autospec=True)
@mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
@mock.patch.object(os, 'environ', autospec=True) @mock.patch.object(os, 'environ', autospec=True)
@mock.patch.object(image, '_get_partition', autospec=True) @mock.patch.object(image, '_get_partition', autospec=True)
def test__install_grub2_prep(self, mock_get_part_uuid, environ_mock, def test__install_grub2_prep(self, mock_get_part_uuid, environ_mock,
mock_is_md_device, mock_execute, mock_md_get_raid_devices, mock_is_md_device,
mock_dispatch): mock_execute, mock_dispatch):
mock_get_part_uuid.side_effect = [self.fake_root_part, mock_get_part_uuid.side_effect = [self.fake_root_part,
self.fake_prep_boot_part] self.fake_prep_boot_part]
environ_mock.get.return_value = '/sbin' environ_mock.get.return_value = '/sbin'
mock_is_md_device.side_effect = [False] mock_is_md_device.side_effect = [False, False]
mock_md_get_raid_devices.return_value = {}
image._install_grub2(self.fake_dev, self.fake_root_uuid, image._install_grub2(self.fake_dev, self.fake_root_uuid,
prep_boot_part_uuid=self.fake_prep_boot_part_uuid) prep_boot_part_uuid=self.fake_prep_boot_part_uuid)
@ -185,16 +190,19 @@ class TestImageExtension(base.IronicAgentTest):
self.assertFalse(mock_dispatch.called) self.assertFalse(mock_dispatch.called)
@mock.patch.object(hardware, 'is_md_device', autospec=True) @mock.patch.object(hardware, 'is_md_device', autospec=True)
@mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
@mock.patch.object(os, 'environ', autospec=True) @mock.patch.object(os, 'environ', autospec=True)
@mock.patch.object(os, 'makedirs', autospec=True) @mock.patch.object(os, 'makedirs', autospec=True)
@mock.patch.object(image, '_get_partition', autospec=True) @mock.patch.object(image, '_get_partition', autospec=True)
def test__install_grub2_uefi(self, mock_get_part_uuid, mkdir_mock, def test__install_grub2_uefi(self, mock_get_part_uuid, mkdir_mock,
environ_mock, mock_is_md_device, environ_mock, mock_md_get_raid_devices,
mock_execute, mock_dispatch): mock_is_md_device, mock_execute,
mock_dispatch):
mock_get_part_uuid.side_effect = [self.fake_root_part, mock_get_part_uuid.side_effect = [self.fake_root_part,
self.fake_efi_system_part] self.fake_efi_system_part]
environ_mock.get.return_value = '/sbin' environ_mock.get.return_value = '/sbin'
mock_is_md_device.return_value = False mock_is_md_device.return_value = False
mock_md_get_raid_devices.return_value = {}
image._install_grub2( image._install_grub2(
self.fake_dev, root_uuid=self.fake_root_uuid, self.fake_dev, root_uuid=self.fake_root_uuid,
@ -245,15 +253,18 @@ class TestImageExtension(base.IronicAgentTest):
self.assertFalse(mock_dispatch.called) self.assertFalse(mock_dispatch.called)
@mock.patch.object(hardware, 'is_md_device', autospec=True) @mock.patch.object(hardware, 'is_md_device', autospec=True)
@mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
@mock.patch.object(os, 'environ', autospec=True) @mock.patch.object(os, 'environ', autospec=True)
@mock.patch.object(os, 'makedirs', autospec=True) @mock.patch.object(os, 'makedirs', autospec=True)
@mock.patch.object(image, '_get_partition', autospec=True) @mock.patch.object(image, '_get_partition', autospec=True)
def test__install_grub2_uefi_umount_fails( def test__install_grub2_uefi_umount_fails(
self, mock_get_part_uuid, mkdir_mock, environ_mock, self, mock_get_part_uuid, mkdir_mock, environ_mock,
mock_is_md_device, mock_execute, mock_dispatch): mock_md_get_raid_devices, mock_is_md_device, mock_execute,
mock_dispatch):
mock_get_part_uuid.side_effect = [self.fake_root_part, mock_get_part_uuid.side_effect = [self.fake_root_part,
self.fake_efi_system_part] self.fake_efi_system_part]
mock_is_md_device.return_value = False mock_is_md_device.return_value = False
mock_md_get_raid_devices.return_value = {}
def umount_raise_func(*args, **kwargs): def umount_raise_func(*args, **kwargs):
if args[0] == 'umount': if args[0] == 'umount':
@ -295,15 +306,18 @@ class TestImageExtension(base.IronicAgentTest):
mock_execute.assert_has_calls(expected) mock_execute.assert_has_calls(expected)
@mock.patch.object(hardware, 'is_md_device', autospec=True) @mock.patch.object(hardware, 'is_md_device', autospec=True)
@mock.patch.object(hardware, 'md_get_raid_devices', autospec=True)
@mock.patch.object(os, 'environ', autospec=True) @mock.patch.object(os, 'environ', autospec=True)
@mock.patch.object(os, 'makedirs', autospec=True) @mock.patch.object(os, 'makedirs', autospec=True)
@mock.patch.object(image, '_get_partition', autospec=True) @mock.patch.object(image, '_get_partition', autospec=True)
def test__install_grub2_uefi_mount_fails( def test__install_grub2_uefi_mount_fails(
self, mock_get_part_uuid, mkdir_mock, environ_mock, self, mock_get_part_uuid, mkdir_mock, environ_mock,
mock_is_md_device, mock_execute, mock_dispatch): mock_is_md_device, mock_md_get_raid_devices, mock_execute,
mock_dispatch):
mock_get_part_uuid.side_effect = [self.fake_root_part, mock_get_part_uuid.side_effect = [self.fake_root_part,
self.fake_efi_system_part] self.fake_efi_system_part]
mock_is_md_device.side_effect = [False] mock_is_md_device.side_effect = [False, False]
mock_md_get_raid_devices.return_value = {}
def mount_raise_func(*args, **kwargs): def mount_raise_func(*args, **kwargs):
if args[0] == 'mount': if args[0] == 'mount':
@ -345,7 +359,7 @@ class TestImageExtension(base.IronicAgentTest):
@mock.patch.object(hardware, 'is_md_device', autospec=True) @mock.patch.object(hardware, 'is_md_device', autospec=True)
def test__get_partition(self, mock_is_md_device, mock_execute, def test__get_partition(self, mock_is_md_device, mock_execute,
mock_dispatch): mock_dispatch):
mock_is_md_device.side_effect = [False] mock_is_md_device.side_effect = [False, False]
lsblk_output = ('''KNAME="test" UUID="" TYPE="disk" lsblk_output = ('''KNAME="test" UUID="" TYPE="disk"
KNAME="test1" UUID="256a39e3-ca3c-4fb8-9cc2-b32eec441f47" TYPE="part" KNAME="test1" UUID="256a39e3-ca3c-4fb8-9cc2-b32eec441f47" TYPE="part"
KNAME="test2" UUID="%s" TYPE="part"''' % self.fake_root_uuid) KNAME="test2" UUID="%s" TYPE="part"''' % self.fake_root_uuid)
@ -364,7 +378,7 @@ class TestImageExtension(base.IronicAgentTest):
@mock.patch.object(hardware, 'is_md_device', autospec=True) @mock.patch.object(hardware, 'is_md_device', autospec=True)
def test__get_partition_no_device_found(self, mock_is_md_device, def test__get_partition_no_device_found(self, mock_is_md_device,
mock_execute, mock_dispatch): mock_execute, mock_dispatch):
mock_is_md_device.side_effect = [False] mock_is_md_device.side_effect = [False, False]
lsblk_output = ('''KNAME="test" UUID="" TYPE="disk" lsblk_output = ('''KNAME="test" UUID="" TYPE="disk"
KNAME="test1" UUID="256a39e3-ca3c-4fb8-9cc2-b32eec441f47" TYPE="part" KNAME="test1" UUID="256a39e3-ca3c-4fb8-9cc2-b32eec441f47" TYPE="part"
KNAME="test2" UUID="" TYPE="part"''') KNAME="test2" UUID="" TYPE="part"''')
@ -412,7 +426,7 @@ class TestImageExtension(base.IronicAgentTest):
@mock.patch.object(hardware, 'is_md_device', autospec=True) @mock.patch.object(hardware, 'is_md_device', autospec=True)
def test__get_partition_command_fail(self, mock_is_md_device, def test__get_partition_command_fail(self, mock_is_md_device,
mock_execute, mock_dispatch): mock_execute, mock_dispatch):
mock_is_md_device.side_effect = [False] mock_is_md_device.side_effect = [False, False]
mock_execute.side_effect = (None, None, mock_execute.side_effect = (None, None,
processutils.ProcessExecutionError('boom')) processutils.ProcessExecutionError('boom'))
self.assertRaises(errors.CommandExecutionError, self.assertRaises(errors.CommandExecutionError,
@ -430,7 +444,7 @@ class TestImageExtension(base.IronicAgentTest):
@mock.patch.object(hardware, 'is_md_device', autospec=True) @mock.patch.object(hardware, 'is_md_device', autospec=True)
def test__get_partition_partuuid(self, mock_is_md_device, mock_execute, def test__get_partition_partuuid(self, mock_is_md_device, mock_execute,
mock_dispatch): mock_dispatch):
mock_is_md_device.side_effect = [False] mock_is_md_device.side_effect = [False, False]
lsblk_output = ('''KNAME="test" UUID="" TYPE="disk" lsblk_output = ('''KNAME="test" UUID="" TYPE="disk"
KNAME="test1" UUID="256a39e3-ca3c-4fb8-9cc2-b32eec441f47" TYPE="part" KNAME="test1" UUID="256a39e3-ca3c-4fb8-9cc2-b32eec441f47" TYPE="part"
KNAME="test2" PARTUUID="%s" TYPE="part"''' % self.fake_root_uuid) KNAME="test2" PARTUUID="%s" TYPE="part"''' % self.fake_root_uuid)