diff --git a/ironic_python_agent/extensions/image.py b/ironic_python_agent/extensions/image.py index ce5d59cd0..ba4710d26 100644 --- a/ironic_python_agent/extensions/image.py +++ b/ironic_python_agent/extensions/image.py @@ -14,6 +14,7 @@ # under the License. import os +import re import shlex import shutil import stat @@ -123,6 +124,16 @@ def _get_partition(device, uuid): 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, prep_boot_part_uuid=None): """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}, 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 utils.execute('chroot %(path)s /bin/sh -c ' '"%(bin)s-mkconfig -o ' diff --git a/ironic_python_agent/hardware.py b/ironic_python_agent/hardware.py index 9126abcca..412f61105 100644 --- a/ironic_python_agent/hardware.py +++ b/ironic_python_agent/hardware.py @@ -224,6 +224,24 @@ def md_restart(raid_device): 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(): """Scan all md devices and assemble RAID arrays from them. diff --git a/ironic_python_agent/tests/unit/extensions/test_image.py b/ironic_python_agent/tests/unit/extensions/test_image.py index 9cf764923..3b6cf9fbb 100644 --- a/ironic_python_agent/tests/unit/extensions/test_image.py +++ b/ironic_python_agent/tests/unit/extensions/test_image.py @@ -92,14 +92,16 @@ class TestImageExtension(base.IronicAgentTest): mock_iscsi_clean.assert_called_once_with(self.fake_dev) @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(image, '_get_partition', autospec=True) def test__install_grub2(self, mock_get_part_uuid, 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.return_value = self.fake_root_part 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) expected = [mock.call('mount', '/dev/fake2', self.fake_dir), @@ -136,15 +138,18 @@ class TestImageExtension(base.IronicAgentTest): self.assertFalse(mock_dispatch.called) @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(image, '_get_partition', autospec=True) def test__install_grub2_prep(self, mock_get_part_uuid, 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, self.fake_prep_boot_part] 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, prep_boot_part_uuid=self.fake_prep_boot_part_uuid) @@ -185,16 +190,19 @@ class TestImageExtension(base.IronicAgentTest): self.assertFalse(mock_dispatch.called) @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, 'makedirs', autospec=True) @mock.patch.object(image, '_get_partition', autospec=True) def test__install_grub2_uefi(self, mock_get_part_uuid, mkdir_mock, - environ_mock, mock_is_md_device, - mock_execute, mock_dispatch): + environ_mock, mock_md_get_raid_devices, + mock_is_md_device, mock_execute, + mock_dispatch): mock_get_part_uuid.side_effect = [self.fake_root_part, self.fake_efi_system_part] environ_mock.get.return_value = '/sbin' mock_is_md_device.return_value = False + mock_md_get_raid_devices.return_value = {} image._install_grub2( self.fake_dev, root_uuid=self.fake_root_uuid, @@ -245,15 +253,18 @@ class TestImageExtension(base.IronicAgentTest): self.assertFalse(mock_dispatch.called) @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, 'makedirs', autospec=True) @mock.patch.object(image, '_get_partition', autospec=True) def test__install_grub2_uefi_umount_fails( 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, self.fake_efi_system_part] mock_is_md_device.return_value = False + mock_md_get_raid_devices.return_value = {} def umount_raise_func(*args, **kwargs): if args[0] == 'umount': @@ -295,15 +306,18 @@ class TestImageExtension(base.IronicAgentTest): mock_execute.assert_has_calls(expected) @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, 'makedirs', autospec=True) @mock.patch.object(image, '_get_partition', autospec=True) def test__install_grub2_uefi_mount_fails( 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, 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): if args[0] == 'mount': @@ -345,7 +359,7 @@ class TestImageExtension(base.IronicAgentTest): @mock.patch.object(hardware, 'is_md_device', autospec=True) def test__get_partition(self, mock_is_md_device, 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" KNAME="test1" UUID="256a39e3-ca3c-4fb8-9cc2-b32eec441f47" TYPE="part" 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) def test__get_partition_no_device_found(self, mock_is_md_device, 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" KNAME="test1" UUID="256a39e3-ca3c-4fb8-9cc2-b32eec441f47" TYPE="part" KNAME="test2" UUID="" TYPE="part"''') @@ -412,7 +426,7 @@ class TestImageExtension(base.IronicAgentTest): @mock.patch.object(hardware, 'is_md_device', autospec=True) def test__get_partition_command_fail(self, mock_is_md_device, 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, processutils.ProcessExecutionError('boom')) self.assertRaises(errors.CommandExecutionError, @@ -430,7 +444,7 @@ class TestImageExtension(base.IronicAgentTest): @mock.patch.object(hardware, 'is_md_device', autospec=True) def test__get_partition_partuuid(self, mock_is_md_device, 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" KNAME="test1" UUID="256a39e3-ca3c-4fb8-9cc2-b32eec441f47" TYPE="part" KNAME="test2" PARTUUID="%s" TYPE="part"''' % self.fake_root_uuid)