summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2020-12-17 22:42:58 +0000
committerGerrit Code Review <review@openstack.org>2020-12-17 22:42:58 +0000
commit433bcffdf2b95199901c658aea109ff7e26d5e9f (patch)
tree42211206c13e87d93b9cadb79d24598af73fd9c3
parent49de16edd290945297fa12ad7d71b6cf6fe7429f (diff)
parenta12a5744b66063816af17769f99ec3f03da0a2d5 (diff)
downloadironic-python-agent-433bcffdf2b95199901c658aea109ff7e26d5e9f.tar.gz
Merge "Add fstab pointer to EFI partition"
-rw-r--r--ironic_python_agent/extensions/image.py25
-rw-r--r--ironic_python_agent/tests/unit/extensions/test_image.py245
-rw-r--r--releasenotes/notes/append-efi-partition-to-fstab-e9f945a4dd19bd7a.yaml8
3 files changed, 271 insertions, 7 deletions
diff --git a/ironic_python_agent/extensions/image.py b/ironic_python_agent/extensions/image.py
index 33cb4a60..846589eb 100644
--- a/ironic_python_agent/extensions/image.py
+++ b/ironic_python_agent/extensions/image.py
@@ -573,6 +573,7 @@ def _install_grub2(device, root_uuid, efi_system_part_uuid=None,
device, path, efi_system_part_uuid,
efi_partitions, efi_partition_mount_point)
if efi_preserved:
+ _append_uefi_to_fstab(path, efi_system_part_uuid)
# Success preserving efi assets
return
else:
@@ -675,6 +676,9 @@ def _install_grub2(device, root_uuid, efi_system_part_uuid=None,
# recommended path.
_configure_grub(device, path)
+ if efi_mounted:
+ _append_uefi_to_fstab(path, efi_system_part_uuid)
+
LOG.info("GRUB2 successfully installed on %s", device)
except processutils.ProcessExecutionError as e:
@@ -822,6 +826,27 @@ def _try_preserve_efi_assets(device, path,
'filesystem. Error: %s', e)
+def _append_uefi_to_fstab(fs_path, efi_system_part_uuid):
+ """Append the efi partition id to the filesystem table.
+
+ :param fs_path:
+ :param efi_system_part_uuid:
+ """
+ fstab_file = os.path.join(fs_path, 'etc/fstab')
+ if not os.path.exists(fstab_file):
+ return
+ try:
+ fstab_string = ("UUID=%s\t/boot/efi\tvfat\tumask=0077\t"
+ "0\t1\n") % efi_system_part_uuid
+ with open(fstab_file, "r+") as fstab:
+ if efi_system_part_uuid not in fstab.read():
+ fstab.writelines(fstab_string)
+ except (OSError, EnvironmentError, IOError) as exc:
+ LOG.debug('Failed to add entry to /etc/fstab. Error %s', exc)
+ LOG.debug('Added entry to /etc/fstab for EFI partition auto-mount '
+ 'with uuid %s', efi_system_part_uuid)
+
+
def _efi_boot_setup(device, efi_system_part_uuid=None, target_boot_mode=None):
"""Identify and setup an EFI bootloader from supplied partition/disk.
diff --git a/ironic_python_agent/tests/unit/extensions/test_image.py b/ironic_python_agent/tests/unit/extensions/test_image.py
index fc8dfb90..55ee60f7 100644
--- a/ironic_python_agent/tests/unit/extensions/test_image.py
+++ b/ironic_python_agent/tests/unit/extensions/test_image.py
@@ -504,13 +504,15 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
@mock.patch.object(os.path, 'exists', lambda *_: True)
@mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False)
+ @mock.patch.object(image, '_append_uefi_to_fstab', 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(image, '_get_partition', autospec=True)
def test__install_grub2(self, mock_get_part_uuid, environ_mock,
mock_md_get_raid_devices, mock_is_md_device,
- mock_execute, mock_dispatch):
+ mock_append_to_fstab, mock_execute,
+ mock_dispatch):
mock_get_part_uuid.return_value = self.fake_root_part
environ_mock.get.return_value = '/sbin'
mock_is_md_device.return_value = False
@@ -562,6 +564,7 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
mock_get_part_uuid.assert_called_once_with(self.fake_dev,
uuid=self.fake_root_uuid)
self.assertFalse(mock_dispatch.called)
+ self.assertFalse(mock_append_to_fstab.called)
@mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False)
@mock.patch.object(hardware, 'is_md_device', autospec=True)
@@ -631,6 +634,7 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
@mock.patch.object(os.path, 'ismount', lambda *_: True)
@mock.patch.object(os.path, 'exists', lambda *_: False)
@mock.patch.object(image, '_is_bootloader_loaded', lambda *_: True)
+ @mock.patch.object(image, '_append_uefi_to_fstab', 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)
@@ -638,8 +642,8 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
@mock.patch.object(image, '_get_partition', autospec=True)
def test__install_grub2_uefi(self, mock_get_part_uuid, mkdir_mock,
environ_mock, mock_md_get_raid_devices,
- mock_is_md_device, mock_execute,
- mock_dispatch):
+ mock_is_md_device, mock_append_to_fstab,
+ 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'
@@ -712,10 +716,218 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
mock_get_part_uuid.assert_any_call(self.fake_dev,
uuid=self.fake_efi_system_part_uuid)
self.assertFalse(mock_dispatch.called)
+ mock_append_to_fstab.assert_called_with(self.fake_dir,
+ self.fake_efi_system_part_uuid)
+
+ @mock.patch.object(os.path, 'ismount', lambda *_: True)
+ @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: True)
+ @mock.patch.object(os.path, 'exists', 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, 'makedirs', autospec=True)
+ @mock.patch.object(image, '_get_partition', autospec=True)
+ def test__install_grub2_uefi_fstab(self, mock_get_part_uuid, mkdir_mock,
+ environ_mock, mock_md_get_raid_devices,
+ mock_is_md_device, mock_exists,
+ 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 = {}
+ mock_exists.side_effect = iter([False, True, False, True, True])
+ with mock.patch('builtins.open', mock.mock_open()) as mock_open:
+ image._install_grub2(
+ self.fake_dev, root_uuid=self.fake_root_uuid,
+ efi_system_part_uuid=self.fake_efi_system_part_uuid,
+ target_boot_mode='uefi')
+ write_calls = [
+ mock.call(self.fake_dir + '/etc/fstab', 'r+'),
+ mock.call().__enter__(),
+ mock.call().read(),
+ mock.call().writelines('UUID=%s\t/boot/efi\tvfat\t'
+ 'umask=0077\t0\t1'
+ '\n' % self.fake_efi_system_part_uuid),
+ mock.call().__exit__(None, None, None)]
+ mock_open.assert_has_calls(write_calls)
+
+ expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
+ mock.call('mount', '-o', 'bind', '/dev',
+ self.fake_dir + '/dev'),
+ mock.call('mount', '-o', 'bind', '/proc',
+ self.fake_dir + '/proc'),
+ mock.call('mount', '-o', 'bind', '/run',
+ self.fake_dir + '/run'),
+ mock.call('mount', '-t', 'sysfs', 'none',
+ self.fake_dir + '/sys'),
+ mock.call(('chroot %s /bin/sh -c "mount -a -t vfat"' %
+ (self.fake_dir)), shell=True,
+ env_variables={
+ 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
+ mock.call('mount', self.fake_efi_system_part,
+ self.fake_dir + '/boot/efi'),
+ mock.call(('chroot %s /bin/sh -c "grub2-install"' %
+ self.fake_dir), shell=True,
+ env_variables={
+ 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
+ mock.call(('chroot %s /bin/sh -c '
+ '"grub2-install --removable"' %
+ self.fake_dir), shell=True,
+ env_variables={
+ 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
+ mock.call(
+ 'umount', self.fake_dir + '/boot/efi',
+ attempts=3, delay_on_retry=True),
+ mock.call('mount', self.fake_efi_system_part,
+ '/tmp/fake-dir/boot/efi'),
+ mock.call(('chroot %s /bin/sh -c '
+ '"grub2-mkconfig -o '
+ '/boot/grub2/grub.cfg"' % self.fake_dir),
+ shell=True,
+ env_variables={
+ 'PATH': '/sbin:/bin:/usr/sbin:/sbin',
+ 'GRUB_DISABLE_OS_PROBER': 'true',
+ 'GRUB_SAVEDEFAULT': 'true'},
+ use_standard_locale=True),
+ mock.call('umount', self.fake_dir + '/boot/efi',
+ attempts=3, delay_on_retry=True),
+ mock.call(('chroot %s /bin/sh -c "umount -a -t vfat"' %
+ (self.fake_dir)), shell=True,
+ env_variables={
+ 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
+ mock.call('umount', self.fake_dir + '/dev',
+ attempts=3, delay_on_retry=True),
+ mock.call('umount', self.fake_dir + '/proc',
+ attempts=3, delay_on_retry=True),
+ mock.call('umount', self.fake_dir + '/run',
+ attempts=3, delay_on_retry=True),
+ mock.call('umount', self.fake_dir + '/sys',
+ attempts=3, delay_on_retry=True),
+ mock.call('umount', self.fake_dir, attempts=3,
+ delay_on_retry=True)]
+ mkdir_mock.assert_called_once_with(self.fake_dir + '/boot/efi')
+ mock_execute.assert_has_calls(expected)
+ mock_get_part_uuid.assert_any_call(self.fake_dev,
+ uuid=self.fake_root_uuid)
+ mock_get_part_uuid.assert_any_call(self.fake_dev,
+ uuid=self.fake_efi_system_part_uuid)
+ self.assertFalse(mock_dispatch.called)
+
+ @mock.patch.object(image, '_efi_boot_setup', lambda *_: False)
+ @mock.patch.object(os.path, 'ismount', lambda *_: True)
+ @mock.patch.object(image, '_is_bootloader_loaded', lambda *_: True)
+ @mock.patch.object(os.path, 'exists', 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, 'makedirs', autospec=True)
+ @mock.patch.object(image, '_get_partition', autospec=True)
+ def test__install_grub2_uefi_no_fstab(
+ self, mock_get_part_uuid,
+ mkdir_mock,
+ environ_mock, mock_md_get_raid_devices,
+ mock_is_md_device, mock_exists,
+ 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 = {}
+ fstab_data = (
+ 'UUID=%s\tpath vfat option' % self.fake_efi_system_part_uuid)
+ mock_exists.side_effect = [True, False, True, True, True, False,
+ True, True]
+ with mock.patch('builtins.open',
+ mock.mock_open(read_data=fstab_data)) as mock_open:
+ image._install_grub2(
+ self.fake_dev, root_uuid=self.fake_root_uuid,
+ efi_system_part_uuid=self.fake_efi_system_part_uuid,
+ target_boot_mode='uefi')
+ write_calls = [
+ mock.call(self.fake_dir + '/etc/fstab', 'r+'),
+ mock.call().__enter__(),
+ mock.call().read(),
+ mock.call().__exit__(None, None, None)]
+ mock_open.assert_has_calls(write_calls)
+
+ expected = [mock.call('mount', '/dev/fake2', self.fake_dir),
+ mock.call('mount', '-o', 'bind', '/dev',
+ self.fake_dir + '/dev'),
+ mock.call('mount', '-o', 'bind', '/proc',
+ self.fake_dir + '/proc'),
+ mock.call('mount', '-o', 'bind', '/run',
+ self.fake_dir + '/run'),
+ mock.call('mount', '-t', 'sysfs', 'none',
+ self.fake_dir + '/sys'),
+ mock.call(('chroot %s /bin/sh -c '
+ '"grub2-mkconfig -o '
+ '/boot/grub2/grub.cfg"' % self.fake_dir),
+ shell=True,
+ env_variables={
+ 'PATH': '/sbin:/bin:/usr/sbin:/sbin',
+ 'GRUB_DISABLE_OS_PROBER': 'true',
+ 'GRUB_SAVEDEFAULT': 'true'},
+ use_standard_locale=True),
+ mock.call('umount', self.fake_dir + '/boot/efi'),
+ # NOTE(TheJulia): chroot mount is for whole disk images
+ mock.call(('chroot %s /bin/sh -c "mount -a -t vfat"' %
+ (self.fake_dir)), shell=True,
+ env_variables={
+ 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
+ mock.call('mount', self.fake_efi_system_part,
+ self.fake_dir + '/boot/efi'),
+ mock.call(('chroot %s /bin/sh -c "grub2-install"' %
+ self.fake_dir), shell=True,
+ env_variables={
+ 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
+ mock.call(('chroot %s /bin/sh -c '
+ '"grub2-install --removable"' %
+ self.fake_dir), shell=True,
+ env_variables={
+ 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
+ mock.call(
+ 'umount', self.fake_dir + '/boot/efi',
+ attempts=3, delay_on_retry=True),
+ mock.call('mount', self.fake_efi_system_part,
+ '/tmp/fake-dir/boot/efi'),
+ mock.call(('chroot %s /bin/sh -c '
+ '"grub2-mkconfig -o '
+ '/boot/grub2/grub.cfg"' % self.fake_dir),
+ shell=True,
+ env_variables={
+ 'PATH': '/sbin:/bin:/usr/sbin:/sbin',
+ 'GRUB_DISABLE_OS_PROBER': 'true',
+ 'GRUB_SAVEDEFAULT': 'true'},
+ use_standard_locale=True),
+ mock.call('umount', self.fake_dir + '/boot/efi',
+ attempts=3, delay_on_retry=True),
+ mock.call(('chroot %s /bin/sh -c "umount -a -t vfat"' %
+ (self.fake_dir)), shell=True,
+ env_variables={
+ 'PATH': '/sbin:/bin:/usr/sbin:/sbin'}),
+ mock.call('umount', self.fake_dir + '/dev',
+ attempts=3, delay_on_retry=True),
+ mock.call('umount', self.fake_dir + '/proc',
+ attempts=3, delay_on_retry=True),
+ mock.call('umount', self.fake_dir + '/run',
+ attempts=3, delay_on_retry=True),
+ mock.call('umount', self.fake_dir + '/sys',
+ attempts=3, delay_on_retry=True),
+ mock.call('umount', self.fake_dir, attempts=3,
+ delay_on_retry=True)]
+ mkdir_mock.assert_called_once_with(self.fake_dir + '/boot/efi')
+ mock_execute.assert_has_calls(expected)
+ mock_get_part_uuid.assert_any_call(self.fake_dev,
+ uuid=self.fake_root_uuid)
+ mock_get_part_uuid.assert_any_call(self.fake_dev,
+ uuid=self.fake_efi_system_part_uuid)
+ self.assertFalse(mock_dispatch.called)
@mock.patch.object(os.path, 'ismount', lambda *_: False)
@mock.patch.object(os, 'listdir', lambda *_: ['file1', 'file2'])
@mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False)
+ @mock.patch.object(image, '_append_uefi_to_fstab', autospec=True)
@mock.patch.object(image, '_efi_boot_setup', autospec=True)
@mock.patch.object(shutil, 'copytree', autospec=True)
@mock.patch.object(os.path, 'exists', autospec=True)
@@ -729,8 +941,10 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
environ_mock, mock_md_get_raid_devices,
mock_is_md_device, mock_exists,
mock_copytree, mock_efi_setup,
- mock_execute, mock_dispatch):
- mock_exists.return_value = True
+ mock_append_to_fstab, mock_execute,
+ mock_dispatch):
+ mock_exists.side_effect = [True, False, True, True, True, False, True,
+ False, False]
mock_efi_setup.return_value = True
mock_get_part_uuid.side_effect = [self.fake_root_part,
self.fake_efi_system_part]
@@ -791,6 +1005,8 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
mock_get_part_uuid.assert_any_call(self.fake_dev,
uuid=self.fake_efi_system_part_uuid)
self.assertFalse(mock_dispatch.called)
+ mock_append_to_fstab.assert_called_with(self.fake_dir,
+ self.fake_efi_system_part_uuid)
@mock.patch.object(os, 'listdir', lambda *_: ['file1', 'file2'])
@mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False)
@@ -878,6 +1094,7 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
@mock.patch.object(os.path, 'ismount', lambda *_: True)
@mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False)
+ @mock.patch.object(image, '_append_uefi_to_fstab', autospec=True)
@mock.patch.object(image, '_preserve_efi_assets', autospec=True)
@mock.patch.object(image, '_efi_boot_setup', autospec=True)
@mock.patch.object(os.path, 'exists', autospec=True)
@@ -892,6 +1109,7 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
mock_is_md_device, mock_exists,
mock_efi_setup,
mock_preserve_efi_assets,
+ mock_append_to_fstab,
mock_execute, mock_dispatch):
mock_exists.return_value = True
mock_efi_setup.side_effect = Exception('meow')
@@ -984,6 +1202,8 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
self.fake_dir + '/boot/efi/EFI',
['/dev/fake1'],
self.fake_dir + '/boot/efi')
+ mock_append_to_fstab.assert_called_with(self.fake_dir,
+ self.fake_efi_system_part_uuid)
@mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False)
@mock.patch.object(os, 'listdir', autospec=True)
@@ -1077,6 +1297,7 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
@mock.patch.object(os.path, 'ismount', lambda *_: True)
@mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False)
@mock.patch.object(os, 'listdir', autospec=True)
+ @mock.patch.object(image, '_append_uefi_to_fstab', autospec=True)
@mock.patch.object(image, '_efi_boot_setup', autospec=True)
@mock.patch.object(shutil, 'copytree', autospec=True)
@mock.patch.object(os.path, 'exists', autospec=True)
@@ -1090,8 +1311,8 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
environ_mock, mock_md_get_raid_devices,
mock_is_md_device, mock_exists,
mock_copytree, mock_efi_setup,
- mock_oslistdir, mock_execute,
- mock_dispatch):
+ mock_append_to_fstab, mock_oslistdir,
+ mock_execute, mock_dispatch):
mock_exists.side_effect = [True, False, False, True, True, True, True]
mock_efi_setup.side_effect = Exception('meow')
mock_oslistdir.return_value = ['file1']
@@ -1174,6 +1395,8 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
mock_get_part_uuid.assert_any_call(self.fake_dev,
uuid=self.fake_efi_system_part_uuid)
self.assertFalse(mock_dispatch.called)
+ mock_append_to_fstab.assert_called_with(self.fake_dir,
+ self.fake_efi_system_part_uuid)
@mock.patch.object(image, '_is_bootloader_loaded', lambda *_: False)
@mock.patch.object(hardware, 'is_md_device', autospec=True)
@@ -2042,3 +2265,11 @@ efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
'\\EFI\\BOOT\\BOOTX64.EFI')]
self.assertIsNone(result)
mock_execute.assert_has_calls(expected)
+
+ @mock.patch.object(os.path, 'exists', lambda *_: True)
+ def test__append_uefi_to_fstab_handles_error(self, mock_execute,
+ mock_dispatch):
+ with mock.patch('builtins.open', mock.mock_open()) as mock_open:
+ mock_open.side_effect = OSError('boom')
+ image._append_uefi_to_fstab(
+ self.fake_dir, 'abcd-efgh')
diff --git a/releasenotes/notes/append-efi-partition-to-fstab-e9f945a4dd19bd7a.yaml b/releasenotes/notes/append-efi-partition-to-fstab-e9f945a4dd19bd7a.yaml
new file mode 100644
index 00000000..13f80f6d
--- /dev/null
+++ b/releasenotes/notes/append-efi-partition-to-fstab-e9f945a4dd19bd7a.yaml
@@ -0,0 +1,8 @@
+---
+fixes:
+ - |
+ The system file system configuration file for Linux machines, the
+ ``/etc/fstab`` file is now updated to include a reference to the
+ EFI partition in the case of a partition image base deployment.
+ Without this reference, images deployed using partition images
+ could end up in situations where upgrading the bootloader could fail.