Explorar el Código

Fix guest grub bootloader entries by temporarily disabling os-prober in osmorphing

Cristian Matiut hace 1 mes
padre
commit
e4fe044600
Se han modificado 2 ficheros con 187 adiciones y 4 borrados
  1. 34 2
      coriolis/osmorphing/base.py
  2. 153 2
      coriolis/tests/osmorphing/test_base.py

+ 34 - 2
coriolis/osmorphing/base.py

@@ -738,9 +738,41 @@ class BaseLinuxOSMorphingTools(BaseOSMorphingTools):
             self.set_grub_value(
                 "GRUB_CMDLINE_LINUX", kernel_cmd, config_obj, replace=replace)
 
+    def _get_grub_default_conf(self):
+        grub_conf = "/etc/default/grub"
+        if self._test_path_chroot(grub_conf):
+            return grub_conf
+        return None
+
+    def _set_grub_os_prober_setting(self, grub_conf, value):
+        """Set or remove GRUB_DISABLE_OS_PROBER in /etc/default/grub."""
+        if value is None:
+            self._exec_cmd_chroot(
+                "sed -i '/^GRUB_DISABLE_OS_PROBER=/d' %s" % grub_conf)
+            return
+        config_obj = self._get_grub_config_obj(grub_conf)
+        replace = "GRUB_DISABLE_OS_PROBER" in config_obj["contents"]
+        self.set_grub_value(
+            "GRUB_DISABLE_OS_PROBER", value, config_obj, replace=replace)
+        self._apply_grub2_config(config_obj, execute_update_grub=False)
+
     def _execute_update_grub(self):
-        update_cmd = self.get_update_grub2_command()
-        self._exec_cmd_chroot(update_cmd)
+        """Regenerate guest GRUB config.
+
+        Temporarily disable os-prober to prevent discovery of host boot
+        entries.
+        """
+        grub_conf = self._get_grub_default_conf()
+        original_value = None
+        if grub_conf:
+            original_value = self._read_grub_config(grub_conf).get(
+                "GRUB_DISABLE_OS_PROBER")
+            self._set_grub_os_prober_setting(grub_conf, "true")
+        try:
+            self._exec_cmd_chroot(self.get_update_grub2_command())
+        finally:
+            if grub_conf:
+                self._set_grub_os_prober_setting(grub_conf, original_value)
 
     def _schedule_grub2_update(self):
         """Flags that the GRUB2 config needs to be regenerated.

+ 153 - 2
coriolis/tests/osmorphing/test_base.py

@@ -1145,12 +1145,19 @@ class BaseLinuxOSMorphingToolsTestBase(test_base.CoriolisBaseTestCase):
                                                   clobber=False)
         mock_set_grub_value.assert_not_called()
 
+    @mock.patch.object(
+        base.BaseLinuxOSMorphingTools, '_get_grub_default_conf',
+        return_value=None,
+    )
     @mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd_chroot')
     @mock.patch.object(
         base.BaseLinuxOSMorphingTools, 'get_update_grub2_command'
     )
-    def test__execute_update_grub(self, mock_get_update_grub2_command,
-                                  mock_exec_cmd_chroot):
+    def test__execute_update_grub(
+            self,
+            mock_get_update_grub2_command,
+            mock_exec_cmd_chroot,
+            _mock_get_grub_default_conf):
         self.os_morphing_tools._execute_update_grub()
 
         mock_get_update_grub2_command.assert_called_once_with()
@@ -1158,6 +1165,150 @@ class BaseLinuxOSMorphingToolsTestBase(test_base.CoriolisBaseTestCase):
             mock_get_update_grub2_command.return_value
         )
 
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path_chroot')
+    def test__get_grub_default_conf_exists(self, mock_test_path_chroot):
+        mock_test_path_chroot.return_value = True
+
+        result = self.os_morphing_tools._get_grub_default_conf()
+
+        mock_test_path_chroot.assert_called_once_with('/etc/default/grub')
+        self.assertEqual(result, '/etc/default/grub')
+
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path_chroot')
+    def test__get_grub_default_conf_missing(self, mock_test_path_chroot):
+        mock_test_path_chroot.return_value = False
+
+        result = self.os_morphing_tools._get_grub_default_conf()
+
+        mock_test_path_chroot.assert_called_once_with('/etc/default/grub')
+        self.assertIsNone(result)
+
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd_chroot')
+    def test__set_grub_os_prober_setting_remove(
+            self, mock_exec_cmd_chroot):
+        grub_conf = '/etc/default/grub'
+
+        self.os_morphing_tools._set_grub_os_prober_setting(grub_conf, None)
+
+        mock_exec_cmd_chroot.assert_called_once_with(
+            "sed -i '/^GRUB_DISABLE_OS_PROBER=/d' %s" % grub_conf)
+
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_apply_grub2_config')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, 'set_grub_value')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_get_grub_config_obj')
+    def test__set_grub_os_prober_setting_append(
+            self, mock_get_grub_config_obj, mock_set_grub_value,
+            mock_apply_grub2_config):
+        grub_conf = '/etc/default/grub'
+        config_obj = {
+            'location': mock.sentinel.location,
+            'source': grub_conf,
+            'contents': {},
+        }
+        mock_get_grub_config_obj.return_value = config_obj
+
+        self.os_morphing_tools._set_grub_os_prober_setting(
+            grub_conf, 'true')
+
+        mock_get_grub_config_obj.assert_called_once_with(grub_conf)
+        mock_set_grub_value.assert_called_once_with(
+            'GRUB_DISABLE_OS_PROBER', 'true', config_obj, replace=False)
+        mock_apply_grub2_config.assert_called_once_with(
+            config_obj, execute_update_grub=False)
+
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_apply_grub2_config')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, 'set_grub_value')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_get_grub_config_obj')
+    def test__set_grub_os_prober_setting_replace(
+            self, mock_get_grub_config_obj, mock_set_grub_value,
+            mock_apply_grub2_config):
+        grub_conf = '/etc/default/grub'
+        config_obj = {
+            'location': mock.sentinel.location,
+            'source': grub_conf,
+            'contents': {'GRUB_DISABLE_OS_PROBER': 'false'},
+        }
+        mock_get_grub_config_obj.return_value = config_obj
+
+        self.os_morphing_tools._set_grub_os_prober_setting(
+            grub_conf, 'true')
+
+        mock_get_grub_config_obj.assert_called_once_with(grub_conf)
+        mock_set_grub_value.assert_called_once_with(
+            'GRUB_DISABLE_OS_PROBER', 'true', config_obj, replace=True)
+        mock_apply_grub2_config.assert_called_once_with(
+            config_obj, execute_update_grub=False)
+
+    @ddt.data(
+        ({}, None),
+        ({'GRUB_DISABLE_OS_PROBER': 'false'}, 'false'),
+        ({'GRUB_DISABLE_OS_PROBER': 'true'}, 'true'),
+    )
+    @ddt.unpack
+    @mock.patch.object(
+        base.BaseLinuxOSMorphingTools, '_set_grub_os_prober_setting')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd_chroot')
+    @mock.patch.object(
+        base.BaseLinuxOSMorphingTools, 'get_update_grub2_command')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_read_grub_config')
+    @mock.patch.object(
+        base.BaseLinuxOSMorphingTools, '_get_grub_default_conf',
+        return_value='/etc/default/grub',
+    )
+    def test__execute_update_grub_toggles_os_prober(
+            self,
+            grub_contents,
+            expected_restore_value,
+            _mock_get_grub_default_conf,
+            mock_read_grub_config,
+            mock_get_update_grub2_command,
+            mock_exec_cmd_chroot,
+            mock_set_grub_os_prober_setting):
+        mock_read_grub_config.return_value = grub_contents
+
+        self.os_morphing_tools._execute_update_grub()
+
+        mock_read_grub_config.assert_called_once_with('/etc/default/grub')
+        mock_set_grub_os_prober_setting.assert_has_calls([
+            mock.call('/etc/default/grub', 'true'),
+            mock.call('/etc/default/grub', expected_restore_value),
+        ])
+        mock_get_update_grub2_command.assert_called_once_with()
+        mock_exec_cmd_chroot.assert_called_once_with(
+            mock_get_update_grub2_command.return_value)
+
+    @mock.patch.object(
+        base.BaseLinuxOSMorphingTools, '_set_grub_os_prober_setting')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd_chroot')
+    @mock.patch.object(
+        base.BaseLinuxOSMorphingTools, 'get_update_grub2_command')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_read_grub_config')
+    @mock.patch.object(
+        base.BaseLinuxOSMorphingTools, '_get_grub_default_conf',
+        return_value='/etc/default/grub',
+    )
+    def test__execute_update_grub_restores_os_prober_on_failure(
+            self,
+            _mock_get_grub_default_conf,
+            mock_read_grub_config,
+            mock_get_update_grub2_command,
+            mock_exec_cmd_chroot,
+            mock_set_grub_os_prober_setting):
+        mock_read_grub_config.return_value = {
+            'GRUB_DISABLE_OS_PROBER': 'false',
+        }
+        mock_exec_cmd_chroot.side_effect = CoriolisTestException()
+
+        self.assertRaises(
+            CoriolisTestException,
+            self.os_morphing_tools._execute_update_grub)
+
+        mock_set_grub_os_prober_setting.assert_has_calls([
+            mock.call('/etc/default/grub', 'true'),
+            mock.call('/etc/default/grub', 'false'),
+        ])
+        mock_get_update_grub2_command.assert_called_once_with()
+
     @mock.patch.object(base.BaseLinuxOSMorphingTools, '_execute_update_grub')
     def test__schedule_grub2_update(self, mock_execute_update_grub):
         self.assertFalse(self.os_morphing_tools._grub2_update_scheduled)