Browse Source

Avoid UEFI GRUB entries on BIOS-booted guests

Signed-off-by: Mihaela Balutoiu <mbalutoiu@cloudbasesolutions.com>
Mihaela Balutoiu 1 month ago
parent
commit
e68ad2e50b

+ 5 - 3
coriolis/osmorphing/base.py

@@ -658,9 +658,11 @@ class BaseLinuxOSMorphingTools(BaseOSMorphingTools):
             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")

+ 21 - 0
coriolis/osmorphing/osmount/base.py

@@ -13,6 +13,7 @@ import uuid
 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 @@ class BaseLinuxOSMountTools(luks_mixin.LinuxLUKSMixin, BaseSSHOSMountTools):
 
         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 @@ class BaseLinuxOSMountTools(luks_mixin.LinuxLUKSMixin, BaseSSHOSMountTools):
                 '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)
 

+ 65 - 0
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 @@ class BaseLinuxOSMountToolsTestCase(test_base.CoriolisBaseTestCase):
             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')

+ 15 - 8
coriolis/tests/osmorphing/test_base.py

@@ -881,24 +881,31 @@ class BaseLinuxOSMorphingToolsTestBase(test_base.CoriolisBaseTestCase):
         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()