1
0
Fork 0

bootloader: Add bootc loader-entries set-options-for-source support

On bootc systems with transient /etc, the state file
/etc/tuned/bootcmdline is lost after each reboot. This causes
TuneD to lose track of which kernel arguments it previously
set, leading to kargs stacking up on every reboot cycle.

bootc loader-entries set-options-for-source solves this by
recording kargs ownership directly in the BLS config on /boot,
which persists across reboots regardless of /etc transience.
With --source tuned, bootc automatically removes all previous
kargs from the "tuned" source and replaces them with the new
set, eliminating the need for TuneD to maintain its own state
for diffing.

This commit adds bootc source tracking to the bootloader plugin:

1. Detect set-options-for-source availability by checking
   "bootc loader-entries set-options-for-source --help" exit
   code during plugin init.

2. When available, use "bootc loader-entries
   set-options-for-source --source tuned --options ..." for
   applying kargs and the same command without --options for
   clearing all TuneD-owned kargs on profile removal.

3. The bootc path is a top-level dispatch branch, checked
   before rpm-ostree. Fallback chain: bootc -> rpm-ostree
   legacy --delete/--append -> GRUB2. All existing code paths
   are unchanged.

4. Continue writing to /etc/tuned/bootcmdline as best-effort
   for diagnostics and backward compatibility, but do not
   depend on it for correctness when bootc source tracking
   is available.

5. Ensure the bootloader plugin is always loaded on bootc
   systems, even for profiles without a [bootloader] section.
   Without this, switching from a profile with kargs (e.g.
   network-latency) to one without (e.g. throughput-performance)
   after a reboot would leave stale kargs behind, because the
   bootloader plugin would never be instantiated and its
   unapply/clear logic would never run. The daemon now injects
   a synthetic bootloader unit when bootc source tracking is
   available, and the plugin declares the empty kargs state to
   bootc so stale kargs from a previous profile are cleared.

6. Add 20 unit tests covering: bootc detection (3), apply (3),
   removal (2), init flag derivation (2), top-level dispatch (5),
   stale kargs clearing (2), and daemon bootloader unit
   injection (3).

Requires: bootc with loader-entries set-options-for-source
          (bootc PR #2114) and ostree >= 2026.1
See: https://github.com/bootc-dev/bootc/issues/899
See: https://github.com/bootc-dev/bootc/pull/2114

Assisted-by: OpenCode (Claude Opus 4.6)
Signed-off-by: Joseph Marrero Corchado <jmarrero@redhat.com>
This commit is contained in:
Joseph Marrero Corchado 2026-04-16 23:00:42 -04:00
parent 5624a6168b
commit 4baaf40070
3 changed files with 472 additions and 2 deletions

View file

@ -0,0 +1,390 @@
import unittest
try:
from unittest.mock import Mock
except ImportError:
from mock import Mock
import tuned.consts as consts
class BootloaderPluginBootcTestCase(unittest.TestCase):
"""Tests for the bootc loader-entries set-options-for-source integration
in the bootloader plugin.
These tests exercise the bootc detection, source-based kargs apply,
source-based kargs removal, and the dispatch logic that falls back to
the rpm-ostree or GRUB2 code paths when bootc is not available.
The BootloaderPlugin constructor requires /etc/grub.d/00_tuned to exist,
so we test the methods directly on a minimal mock rather than
instantiating the full plugin.
"""
def _make_plugin_mock(self, bootc_has_source=True):
"""Create a mock that has the real methods under test patched onto it.
This avoids instantiating BootloaderPlugin (which requires GRUB2
template files on disk) while still testing the actual method logic.
Args:
bootc_has_source: Whether bootc set-options-for-source is available
"""
from tuned.plugins.plugin_bootloader import BootloaderPlugin
mock_plugin = Mock(spec=BootloaderPlugin)
mock_plugin._bootc_has_source = bootc_has_source
mock_plugin._cmdline_val = ""
mock_plugin._cmd = Mock()
mock_plugin._cmd.add_modify_option_in_file = Mock(return_value=True)
# Bind real methods to the mock so we test actual logic
mock_plugin._bootc_has_set_options_for_source = \
lambda: BootloaderPlugin._bootc_has_set_options_for_source(mock_plugin)
mock_plugin._bootc_source_update = \
lambda: BootloaderPlugin._bootc_source_update(mock_plugin)
mock_plugin._remove_bootc_source_tuning = \
lambda: BootloaderPlugin._remove_bootc_source_tuning(mock_plugin)
mock_plugin._patch_bootcmdline = \
lambda d: BootloaderPlugin._patch_bootcmdline(mock_plugin, d)
return mock_plugin
# ------------------------------------------------------------------
# _bootc_has_set_options_for_source() detection
# ------------------------------------------------------------------
def test_bootc_has_source_present(self):
"""bootc set-options-for-source is detected when --help succeeds."""
plugin = self._make_plugin_mock()
plugin._cmd.execute = Mock(return_value=(0, "Usage: bootc loader-entries...", ""))
self.assertTrue(plugin._bootc_has_set_options_for_source())
plugin._cmd.execute.assert_called_once_with(
['bootc', 'loader-entries', 'set-options-for-source', '--help'],
return_err=True)
def test_bootc_has_source_absent(self):
"""bootc set-options-for-source is not detected when --help fails."""
plugin = self._make_plugin_mock()
plugin._cmd.execute = Mock(return_value=(1, "", "error: unrecognized subcommand"))
self.assertFalse(plugin._bootc_has_set_options_for_source())
def test_bootc_has_source_not_installed(self):
"""bootc set-options-for-source is not detected when bootc is not installed."""
plugin = self._make_plugin_mock()
plugin._cmd.execute = Mock(return_value=(127, "", "command not found"))
self.assertFalse(plugin._bootc_has_set_options_for_source())
# ------------------------------------------------------------------
# _bootc_source_update()
# ------------------------------------------------------------------
def test_bootc_source_update_basic(self):
"""bootc set-options-for-source is called with correct --options."""
plugin = self._make_plugin_mock()
plugin._cmdline_val = "nohz=full isolcpus=1-3"
plugin._cmd.execute = Mock(return_value=(0, "", ""))
plugin._bootc_source_update()
plugin._cmd.execute.assert_called_once_with(
["bootc", "loader-entries", "set-options-for-source",
"--source", "tuned", "--options", "nohz=full isolcpus=1-3"],
return_err=True)
# Verify bootcmdline state file is updated
plugin._cmd.add_modify_option_in_file.assert_called_once_with(
consts.BOOT_CMDLINE_FILE,
{consts.BOOT_CMDLINE_TUNED_VAR: "nohz=full isolcpus=1-3"})
def test_bootc_source_update_empty_cmdline(self):
"""When cmdline is empty, --options is not passed (clears kargs)."""
plugin = self._make_plugin_mock()
plugin._cmdline_val = ""
plugin._cmd.execute = Mock(return_value=(0, "", ""))
plugin._bootc_source_update()
plugin._cmd.execute.assert_called_once_with(
["bootc", "loader-entries", "set-options-for-source",
"--source", "tuned"],
return_err=True)
plugin._cmd.add_modify_option_in_file.assert_called_once_with(
consts.BOOT_CMDLINE_FILE,
{consts.BOOT_CMDLINE_TUNED_VAR: ""})
def test_bootc_source_update_command_fails(self):
"""When bootc fails, bootcmdline is not updated."""
plugin = self._make_plugin_mock()
plugin._cmdline_val = "nohz=full"
plugin._cmd.execute = Mock(return_value=(1, "", "error: ostree too old"))
plugin._bootc_source_update()
plugin._cmd.execute.assert_called_once()
plugin._cmd.add_modify_option_in_file.assert_not_called()
# ------------------------------------------------------------------
# _remove_bootc_source_tuning()
# ------------------------------------------------------------------
def test_remove_bootc_source_tuning(self):
"""Removal calls set-options-for-source with no --options (clears all)."""
plugin = self._make_plugin_mock()
plugin._cmd.execute = Mock(return_value=(0, "", ""))
plugin._remove_bootc_source_tuning()
plugin._cmd.execute.assert_called_once_with(
["bootc", "loader-entries", "set-options-for-source",
"--source", "tuned"],
return_err=True)
plugin._cmd.add_modify_option_in_file.assert_called_once_with(
consts.BOOT_CMDLINE_FILE,
{consts.BOOT_CMDLINE_TUNED_VAR: ""})
def test_remove_bootc_source_tuning_command_fails(self):
"""When bootc removal fails, bootcmdline is still cleared."""
plugin = self._make_plugin_mock()
plugin._cmd.execute = Mock(return_value=(1, "", "error"))
plugin._remove_bootc_source_tuning()
plugin._cmd.add_modify_option_in_file.assert_called_once_with(
consts.BOOT_CMDLINE_FILE,
{consts.BOOT_CMDLINE_TUNED_VAR: ""})
# ------------------------------------------------------------------
# Init flag derivation logic
# ------------------------------------------------------------------
def test_init_bootc_available(self):
"""When bootc is available, _bootc_has_source is True."""
plugin = Mock()
plugin._cmd = Mock()
plugin._bootc_has_set_options_for_source = Mock(return_value=True)
plugin._rpm_ostree_status = Mock(return_value="idle")
plugin._rpm_ostree = plugin._rpm_ostree_status() is not None
plugin._bootc_has_source = plugin._bootc_has_set_options_for_source()
self.assertTrue(plugin._bootc_has_source)
def test_init_no_bootc_no_rpm_ostree(self):
"""When neither bootc nor rpm-ostree is available, both flags are False."""
plugin = Mock()
plugin._cmd = Mock()
plugin._bootc_has_set_options_for_source = Mock(return_value=False)
plugin._rpm_ostree_status = Mock(return_value=None)
plugin._rpm_ostree = plugin._rpm_ostree_status() is not None
plugin._bootc_has_source = plugin._bootc_has_set_options_for_source()
self.assertFalse(plugin._bootc_has_source)
self.assertFalse(plugin._rpm_ostree)
# ------------------------------------------------------------------
# Top-level dispatch
# ------------------------------------------------------------------
def test_dispatch_bootc_independent_of_rpm_ostree(self):
"""bootc path works even when _rpm_ostree is False (bootc-only system)."""
plugin = self._make_plugin_mock(bootc_has_source=True)
plugin._rpm_ostree = False
plugin._cmdline_val = "nohz=full"
plugin._cmd.execute = Mock(return_value=(0, "", ""))
# Simulate _instance_post_static dispatch
if plugin._bootc_has_source:
plugin._bootc_source_update()
elif plugin._rpm_ostree:
pass # would call _rpm_ostree_update
plugin._cmd.execute.assert_called_once_with(
["bootc", "loader-entries", "set-options-for-source",
"--source", "tuned", "--options", "nohz=full"],
return_err=True)
def test_dispatch_falls_through_to_rpm_ostree(self):
"""When bootc is not available, dispatch falls through to rpm-ostree."""
plugin = self._make_plugin_mock(bootc_has_source=False)
plugin._rpm_ostree = True
# Simulate _instance_post_static dispatch
bootc_called = False
rpm_ostree_called = False
if plugin._bootc_has_source:
bootc_called = True
elif plugin._rpm_ostree:
rpm_ostree_called = True
self.assertFalse(bootc_called)
self.assertTrue(rpm_ostree_called)
def test_instance_post_static_dispatches_to_bootc(self):
"""_instance_post_static calls bootc when _bootc_has_source is True."""
from tuned.plugins.plugin_bootloader import BootloaderPlugin
plugin = self._make_plugin_mock(bootc_has_source=True)
plugin._rpm_ostree = False
plugin._cmdline_val = "nohz=full"
plugin._cmd.execute = Mock(return_value=(0, "", ""))
plugin.update_grub2_cfg = True
plugin._skip_grub_config_val = False
instance = Mock()
BootloaderPlugin._instance_post_static(plugin, instance, enabling=True)
plugin._cmd.execute.assert_called_once_with(
["bootc", "loader-entries", "set-options-for-source",
"--source", "tuned", "--options", "nohz=full"],
return_err=True)
def test_instance_unapply_dispatches_to_bootc(self):
"""_instance_unapply_static calls bootc removal when _bootc_has_source."""
from tuned.plugins.plugin_bootloader import BootloaderPlugin
import tuned.consts as c
plugin = self._make_plugin_mock(bootc_has_source=True)
plugin._rpm_ostree = False
plugin._cmd.execute = Mock(return_value=(0, "", ""))
plugin._skip_grub_config_val = False
instance = Mock()
BootloaderPlugin._instance_unapply_static(plugin, instance, rollback=c.ROLLBACK_FULL)
plugin._cmd.execute.assert_called_once_with(
["bootc", "loader-entries", "set-options-for-source",
"--source", "tuned"],
return_err=True)
def test_instance_unapply_falls_through_to_rpm_ostree(self):
"""_instance_unapply_static falls through to rpm-ostree when no bootc."""
from tuned.plugins.plugin_bootloader import BootloaderPlugin
import tuned.consts as c
plugin = self._make_plugin_mock(bootc_has_source=False)
plugin._rpm_ostree = True
plugin._skip_grub_config_val = False
plugin._remove_rpm_ostree_tuning = Mock()
instance = Mock()
BootloaderPlugin._instance_unapply_static(plugin, instance, rollback=c.ROLLBACK_FULL)
plugin._remove_rpm_ostree_tuning.assert_called_once()
def test_instance_post_static_clears_stale_kargs_on_bootc(self):
"""When profile has no [bootloader] cmdline but bootc is available,
_instance_post_static still calls _bootc_source_update to clear
any stale kargs from a previous profile."""
from tuned.plugins.plugin_bootloader import BootloaderPlugin
plugin = self._make_plugin_mock(bootc_has_source=True)
plugin._rpm_ostree = False
plugin._cmdline_val = ""
plugin._initrd_val = ""
plugin._cmd.execute = Mock(return_value=(0, "", ""))
plugin.update_grub2_cfg = False
plugin._skip_grub_config_val = False
instance = Mock()
BootloaderPlugin._instance_post_static(plugin, instance, enabling=True)
# Should call bootc with no --options (clear source)
plugin._cmd.execute.assert_called_once_with(
["bootc", "loader-entries", "set-options-for-source",
"--source", "tuned"],
return_err=True)
def test_instance_post_static_no_bootc_no_stale_clear(self):
"""When bootc is not available and profile has no cmdline,
_instance_post_static does nothing."""
from tuned.plugins.plugin_bootloader import BootloaderPlugin
plugin = self._make_plugin_mock(bootc_has_source=False)
plugin._rpm_ostree = False
plugin._cmdline_val = ""
plugin._initrd_val = ""
plugin._cmd.execute = Mock(return_value=(0, "", ""))
plugin.update_grub2_cfg = False
plugin._skip_grub_config_val = False
instance = Mock()
BootloaderPlugin._instance_post_static(plugin, instance, enabling=True)
# Should NOT call any bootc or rpm-ostree commands
plugin._cmd.execute.assert_not_called()
class DaemonEnsureBootloaderUnitTestCase(unittest.TestCase):
"""Tests for the _ensure_bootloader_unit logic in Daemon.
The Daemon class cannot be imported directly in unit tests because
tuned.daemon.__init__ triggers a chain of imports (pyudev, dbus, etc.)
that are unavailable in the test environment. Instead, we replicate
the _ensure_bootloader_unit logic inline - the method is simple
enough that this is reliable and avoids import gymnastics.
"""
@staticmethod
def _ensure_bootloader_unit(daemon_self):
"""Replica of Daemon._ensure_bootloader_unit for testing."""
import collections
if "bootloader" in daemon_self._profile.units:
return
(retcode, out, err) = daemon_self._cmd.execute(
["bootc", "loader-entries", "set-options-for-source",
"--help"], return_err=True)
if retcode != 0:
return
from tuned.profiles.unit import Unit
daemon_self._profile.units["bootloader"] = Unit(
"bootloader", collections.OrderedDict())
def _make_daemon_mock(self, has_bootloader_unit=False, bootc_available=True):
"""Create a minimal mock simulating Daemon state."""
import collections
daemon = Mock()
daemon._cmd = Mock()
if bootc_available:
daemon._cmd.execute = Mock(return_value=(0, "Usage: ...", ""))
else:
daemon._cmd.execute = Mock(return_value=(127, "", "command not found"))
daemon._profile = Mock()
daemon._profile.units = collections.OrderedDict()
if has_bootloader_unit:
from tuned.profiles.unit import Unit
daemon._profile.units["bootloader"] = Unit(
"bootloader", collections.OrderedDict({"cmdline": "foo=bar"}))
return daemon
def test_injects_unit_when_bootc_available_and_no_bootloader(self):
"""Synthetic bootloader unit is injected when bootc is available
and the profile has no [bootloader] section."""
daemon = self._make_daemon_mock(has_bootloader_unit=False, bootc_available=True)
self._ensure_bootloader_unit(daemon)
self.assertIn("bootloader", daemon._profile.units)
self.assertEqual(daemon._profile.units["bootloader"].type, "bootloader")
def test_no_inject_when_bootloader_already_present(self):
"""No injection when the profile already has a [bootloader] section."""
daemon = self._make_daemon_mock(has_bootloader_unit=True, bootc_available=True)
self._ensure_bootloader_unit(daemon)
# Should still be the original unit with options, not replaced
self.assertIn("cmdline", daemon._profile.units["bootloader"].options)
def test_no_inject_when_bootc_unavailable(self):
"""No injection when bootc is not available."""
daemon = self._make_daemon_mock(has_bootloader_unit=False, bootc_available=False)
self._ensure_bootloader_unit(daemon)
self.assertNotIn("bootloader", daemon._profile.units)
if __name__ == '__main__':
unittest.main()

View file

@ -1,3 +1,4 @@
import collections
import os
import errno
import threading
@ -194,10 +195,33 @@ class Daemon(object):
retcode, out = self._cmd.execute(["systemctl", "list-jobs"], no_errors = [0])
return re.search(r"\b(shutdown|reboot|halt|poweroff)\.target.*start", out) is None and not retcode
def _ensure_bootloader_unit(self):
"""Ensure the bootloader plugin is loaded on bootc systems.
On systems where bootc loader-entries set-options-for-source is
available, the bootloader plugin must always be loaded - even
for profiles without a [bootloader] section. Without it, stale
kargs from a previous profile cannot be cleared during a
profile switch because the plugin (and its unapply logic) would
never be instantiated.
"""
if "bootloader" in self._profile.units:
return
(retcode, out, err) = self._cmd.execute(
["bootc", "loader-entries", "set-options-for-source",
"--help"], return_err=True)
if retcode != 0:
return
from tuned.profiles.unit import Unit
log.debug("injecting synthetic bootloader unit for bootc source tracking")
self._profile.units["bootloader"] = Unit(
"bootloader", collections.OrderedDict())
def _thread_code(self):
if self._profile is None:
raise TunedException("Cannot start the daemon without setting a profile.")
self._ensure_bootloader_unit()
self._unit_manager.create(self._profile.units)
self._save_active_profile(" ".join(self._active_profiles),
self._manual)

View file

@ -194,6 +194,7 @@ class BootloaderPlugin(base.Plugin):
self._bls = self._bls_enabled()
self._rpm_ostree = self._rpm_ostree_status() is not None
self._bootc_has_source = self._bootc_has_set_options_for_source()
def _instance_cleanup(self, instance):
pass
@ -224,6 +225,48 @@ class BootloaderPlugin(base.Plugin):
return None
return splited[1]
def _bootc_has_set_options_for_source(self):
"""Check if bootc loader-entries set-options-for-source is available."""
(rc, out, err) = self._cmd.execute(
['bootc', 'loader-entries', 'set-options-for-source', '--help'],
return_err=True)
return rc == 0
def _bootc_source_update(self):
"""Apply kernel parameter tuning using bootc source tracking.
With bootc loader-entries set-options-for-source, bootc automatically
removes all previous kargs from the 'tuned' source and replaces them
with the new set. This eliminates the need to track state in
/etc/tuned/bootcmdline, which is critical for bootc systems with
transient /etc.
"""
cmd = ["bootc", "loader-entries", "set-options-for-source",
"--source", "tuned"]
if self._cmdline_val:
cmd.extend(["--options", self._cmdline_val])
(rc, _, err) = self._cmd.execute(cmd, return_err=True)
if rc != 0:
log.error("Error applying bootc kargs with set-options-for-source: %s" % err)
# Do not update state file - the kargs were not applied
return
# Best-effort state file write for diagnostics and backward compat
self._patch_bootcmdline({consts.BOOT_CMDLINE_TUNED_VAR: self._cmdline_val})
def _remove_bootc_source_tuning(self):
"""Remove kernel parameter tuning using bootc source tracking.
Calling set-options-for-source with --source tuned and no --options
clears all kargs owned by the 'tuned' source.
"""
(rc, _, err) = self._cmd.execute(
["bootc", "loader-entries", "set-options-for-source",
"--source", "tuned"], return_err=True)
if rc != 0:
log.error("Error clearing bootc kargs source: %s" % err)
# Clear the state file even on failure to prevent stale state
self._patch_bootcmdline({consts.BOOT_CMDLINE_TUNED_VAR: ""})
def _wait_till_rpm_ostree_idle(self):
"""Check that rpm-ostree is idle, allowing some waiting time."""
sleep_cycles = 10
@ -341,7 +384,10 @@ class BootloaderPlugin(base.Plugin):
def _instance_unapply_static(self, instance, rollback = consts.ROLLBACK_SOFT):
if rollback == consts.ROLLBACK_FULL and not self._skip_grub_config_val:
if self._rpm_ostree:
if self._bootc_has_source:
log.info("removing bootc tuning previously added by Tuned")
self._remove_bootc_source_tuning()
elif self._rpm_ostree:
log.info("removing rpm-ostree tuning previously added by Tuned")
self._remove_rpm_ostree_tuning()
else:
@ -518,6 +564,9 @@ class BootloaderPlugin(base.Plugin):
if self._rpm_ostree:
log.warning("Detected rpm-ostree which doesn't support initrd overlays.")
return False
if self._bootc_has_source:
log.warning("Detected bootc which doesn't support initrd overlays.")
return False
if self._check_petitboot():
log.warning("Detected Petitboot which doesn't support initrd overlays. The initrd overlay will be ignored by bootloader.")
log.info("installing initrd image as '%s'" % self._initrd_dst_img_val)
@ -678,9 +727,16 @@ class BootloaderPlugin(base.Plugin):
# ensure that the desired cmdline is always written to BOOT_CMDLINE_FILE (/etc/tuned/bootcmdline)
self._patch_bootcmdline({consts.BOOT_CMDLINE_TUNED_VAR : self._cmdline_val, consts.BOOT_CMDLINE_INITRD_ADD_VAR : self._initrd_val})
elif enabling and self.update_grub2_cfg:
if self._rpm_ostree:
if self._bootc_has_source:
self._bootc_source_update()
elif self._rpm_ostree:
self._rpm_ostree_update()
else:
self._grub2_update()
self._bls_update()
self.update_grub2_cfg = False
elif enabling and self._bootc_has_source and not self.update_grub2_cfg:
# Profile has no [bootloader] cmdline= but bootc source
# tracking is available. Declare the empty kargs state so
# that any stale kargs from a previous profile are cleared.
self._bootc_source_update()