From 34546907e0d1284cacdcc69b5cdc58bf731464b3 Mon Sep 17 00:00:00 2001 From: Mihaela Balutoiu Date: Tue, 7 Jul 2026 09:00:18 +0300 Subject: [PATCH] Avoid UEFI GRUB entries on BIOS-booted guests Signed-off-by: Mihaela Balutoiu --- coriolis/osmorphing/base.py | 8 ++- coriolis/osmorphing/osmount/base.py | 21 ++++++ .../tests/osmorphing/osmount/test_base.py | 65 +++++++++++++++++++ coriolis/tests/osmorphing/test_base.py | 23 ++++--- 4 files changed, 106 insertions(+), 11 deletions(-) diff --git a/coriolis/osmorphing/base.py b/coriolis/osmorphing/base.py index 7e7c08cdd..fe0e830bb 100644 --- a/coriolis/osmorphing/base.py +++ b/coriolis/osmorphing/base.py @@ -550,9 +550,11 @@ def _ensure_cloud_init_not_disabled(self): self._exec_cmd_chroot( "sed -i '/cloud-init=disabled/d' %s" % system_conf_disabler) if self._test_path(grub_conf_disabler): - self._exec_cmd_chroot( - "sed -i '/cloud-init=disabled/d' %s" % grub_conf_disabler) - self._schedule_grub2_update() + contents = self._read_file_sudo(grub_conf_disabler) + if "cloud-init=disabled" in contents: + self._exec_cmd_chroot( + "sed -i '/cloud-init=disabled/d' %s" % grub_conf_disabler) + self._schedule_grub2_update() def _reset_cloud_init_run(self): self._exec_cmd_chroot("cloud-init clean --logs") diff --git a/coriolis/osmorphing/osmount/base.py b/coriolis/osmorphing/osmount/base.py index 4f1509319..1658b43ca 100644 --- a/coriolis/osmorphing/osmount/base.py +++ b/coriolis/osmorphing/osmount/base.py @@ -13,6 +13,7 @@ from oslo_log import log as logging from six import with_metaclass +from coriolis import constants from coriolis import exception from coriolis.osmorphing.osmount import luks_mixin from coriolis import utils @@ -504,6 +505,24 @@ def _find_dev_with_contents(self, devices, all_files=None, return dev_name + def _mask_minion_efi_firmware(self, os_root_dir): + firmware_type = self._osmorphing_info.get("firmware_type") + if firmware_type is None: + firmware_type = self._osmorphing_info.get( + "osmorphing_parameters", {}).get("firmware_type") + if firmware_type != constants.FIRMWARE_TYPE_BIOS: + return + efi_dir = os.path.join(os_root_dir, "sys/firmware/efi") + if not utils.test_ssh_path(self._ssh, efi_dir): + return + LOG.info( + "OSMorphing minion machine is UEFI-booted, but the OS being " + "migrated is BIOS-booted. Masking '/sys/firmware' in the " + "OSMorphing chroot at '%s'", os_root_dir) + self._exec_cmd( + "sudo mount -t tmpfs tmpfs %s" % os.path.join( + os_root_dir, "sys/firmware")) + def _find_and_mount_root(self, devices): files = ["etc", "bin", "sbin", "boot"] os_root_dir = None @@ -564,6 +583,8 @@ def _find_and_mount_root(self, devices): 'sudo mount -o bind /%(dir)s/ %(mount_dir)s' % {'dir': directory, 'mount_dir': mount_dir}) + self._mask_minion_efi_firmware(os_root_dir) + if os_root_device in devices: devices.remove(os_root_device) diff --git a/coriolis/tests/osmorphing/osmount/test_base.py b/coriolis/tests/osmorphing/osmount/test_base.py index 5258fa3f5..e4ea24946 100644 --- a/coriolis/tests/osmorphing/osmount/test_base.py +++ b/coriolis/tests/osmorphing/osmount/test_base.py @@ -4,6 +4,7 @@ import logging from unittest import mock +from coriolis import constants from coriolis import exception from coriolis.osmorphing.osmount import base from coriolis.tests import test_base @@ -850,6 +851,70 @@ def test__find_and_mount_root_exec_cmd_exception( devices, all_files=['etc', 'bin', 'sbin', 'boot']) mock_test_ssh_path.assert_not_called() + @mock.patch.object(base.utils, 'test_ssh_path') + @mock.patch.object(base.BaseSSHOSMountTools, '_exec_cmd') + def test__mask_minion_efi_firmware_bios_top_level( + self, mock_exec_cmd, mock_test_ssh_path): + self.base_os_mount_tools._osmorphing_info = { + "firmware_type": constants.FIRMWARE_TYPE_BIOS} + mock_test_ssh_path.return_value = True + + self.base_os_mount_tools._mask_minion_efi_firmware(self.os_root_dir) + + mock_test_ssh_path.assert_called_once_with( + self.base_os_mount_tools._ssh, "/root/sys/firmware/efi") + mock_exec_cmd.assert_called_once_with( + "sudo mount -t tmpfs tmpfs /root/sys/firmware") + + @mock.patch.object(base.utils, 'test_ssh_path') + @mock.patch.object(base.BaseSSHOSMountTools, '_exec_cmd') + def test__mask_minion_efi_firmware_bios_nested_params( + self, mock_exec_cmd, mock_test_ssh_path): + self.base_os_mount_tools._osmorphing_info = { + "osmorphing_parameters": { + "firmware_type": constants.FIRMWARE_TYPE_BIOS}} + mock_test_ssh_path.return_value = True + + self.base_os_mount_tools._mask_minion_efi_firmware(self.os_root_dir) + + mock_exec_cmd.assert_called_once_with( + "sudo mount -t tmpfs tmpfs /root/sys/firmware") + + @mock.patch.object(base.utils, 'test_ssh_path') + @mock.patch.object(base.BaseSSHOSMountTools, '_exec_cmd') + def test__mask_minion_efi_firmware_no_efi_in_chroot( + self, mock_exec_cmd, mock_test_ssh_path): + self.base_os_mount_tools._osmorphing_info = { + "firmware_type": constants.FIRMWARE_TYPE_BIOS} + mock_test_ssh_path.return_value = False + + self.base_os_mount_tools._mask_minion_efi_firmware(self.os_root_dir) + + mock_exec_cmd.assert_not_called() + + @mock.patch.object(base.utils, 'test_ssh_path') + @mock.patch.object(base.BaseSSHOSMountTools, '_exec_cmd') + def test__mask_minion_efi_firmware_uefi_guest( + self, mock_exec_cmd, mock_test_ssh_path): + self.base_os_mount_tools._osmorphing_info = { + "firmware_type": constants.FIRMWARE_TYPE_EFI} + + self.base_os_mount_tools._mask_minion_efi_firmware(self.os_root_dir) + + mock_test_ssh_path.assert_not_called() + mock_exec_cmd.assert_not_called() + + @mock.patch.object(base.utils, 'test_ssh_path') + @mock.patch.object(base.BaseSSHOSMountTools, '_exec_cmd') + def test__mask_minion_efi_firmware_unknown_firmware( + self, mock_exec_cmd, mock_test_ssh_path): + self.base_os_mount_tools._osmorphing_info = {} + + self.base_os_mount_tools._mask_minion_efi_firmware(self.os_root_dir) + + mock_test_ssh_path.assert_not_called() + mock_exec_cmd.assert_not_called() + @mock.patch.object(base.utils, 'check_fs') @mock.patch.object(base.BaseSSHOSMountTools, '_exec_cmd') @mock.patch.object(base.BaseLinuxOSMountTools, '_get_vgs') diff --git a/coriolis/tests/osmorphing/test_base.py b/coriolis/tests/osmorphing/test_base.py index e4ff65e9b..9e7b3fb74 100644 --- a/coriolis/tests/osmorphing/test_base.py +++ b/coriolis/tests/osmorphing/test_base.py @@ -718,24 +718,31 @@ def test__disable_installer_cloud_config_no_file( mock__exec_cmd_chroot.assert_not_called() @ddt.data( - ((False, False, False), [], False), - ((True, True, False), [ + ((False, False, False), "", [], False), + ((True, True, False), "", [ "rm /etc/cloud/cloud-init.disabled", "sed -i '/cloud-init=disabled/d' /etc/systemd/system.conf", ], False), - ((False, False, True), [ - "sed -i '/cloud-init=disabled/d' /etc/default/grub", - ], True) + ((False, False, True), + 'GRUB_CMDLINE_LINUX="console=ttyS0 cloud-init=disabled"', + ["sed -i '/cloud-init=disabled/d' /etc/default/grub"], + True), + ((False, False, True), + 'GRUB_CMDLINE_LINUX="console=ttyS0"', + [], + False), ) @ddt.unpack + @mock.patch.object(base.BaseLinuxOSMorphingTools, "_read_file_sudo") @mock.patch.object(base.BaseLinuxOSMorphingTools, "_schedule_grub2_update") @mock.patch.object(base.BaseLinuxOSMorphingTools, "_exec_cmd_chroot") @mock.patch.object(base.BaseLinuxOSMorphingTools, "_test_path") def test__ensure_cloud_init_not_disabled( - self, test_path_results, expected_cmds, updates_grub, - mock__test_path, mock__exec_cmd_chroot, - mock__schedule_grub2_update): + self, test_path_results, grub_defaults_contents, expected_cmds, + updates_grub, mock__test_path, mock__exec_cmd_chroot, + mock__schedule_grub2_update, mock__read_file_sudo): mock__test_path.side_effect = test_path_results + mock__read_file_sudo.return_value = grub_defaults_contents self.os_morphing_tools._ensure_cloud_init_not_disabled()