Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatic bootupd support #5342

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/release-notes/bootupd.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
:Type: OSTree
:Summary: Preliminary support for bootable ostree containers

:Description:
Anaconda can now correctly detect and use the bootupd bootloader used in
bootable ostree containers. When the installed container includes the ``bootupctl`` tool, it
is used instead of installing the ``grub2`` bootloader by Anaconda.

:Links:
- https://github.com/rhinstaller/anaconda/pull/5342
32 changes: 30 additions & 2 deletions pyanaconda/modules/payloads/payload/rpm_ostree/installation.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@
from pyanaconda.core.i18n import _
from pyanaconda.core.path import set_system_root, make_directories
from pyanaconda.core.util import execWithRedirect
from pyanaconda.modules.common.errors.installation import PayloadInstallationError
from pyanaconda.modules.common.errors.installation import PayloadInstallationError, \
BootloaderInstallationError
from pyanaconda.modules.common.task import Task
from pyanaconda.modules.common.constants.objects import DEVICE_TREE, BOOTLOADER
from pyanaconda.modules.common.constants.services import STORAGE
from pyanaconda.modules.common.structures.storage import DeviceData
from pyanaconda.modules.payloads.payload.rpm_ostree.util import have_bootupd

import gi
gi.require_version("OSTree", "1.0")
Expand Down Expand Up @@ -496,9 +498,35 @@ def name(self):
return "Configure OSTree bootloader"

def run(self):
self._move_grub_config()
if have_bootupd(self._sysroot):
self._install_bootupd()
else:
self._move_grub_config()
self._set_kargs()

def _install_bootupd(self):
bootloader = STORAGE.get_proxy(BOOTLOADER)
device_tree = STORAGE.get_proxy(DEVICE_TREE)
dev_data = DeviceData.from_structure(device_tree.GetDeviceData(bootloader.Drive))

rc = execWithRedirect(
"bootupctl",
[
"backend",
"install",
"--auto",
"--with-static-configs",
"--device",
dev_data.path,
"/",
],
root=self._sysroot
)

if rc:
raise BootloaderInstallationError(
"failed to write boot loader configuration")

def _move_grub_config(self):
"""If using GRUB2, move its config file, also with a compatibility symlink."""
boot_grub2_cfg = self._sysroot + '/boot/grub2/grub.cfg'
Expand Down
27 changes: 27 additions & 0 deletions pyanaconda/modules/payloads/payload/rpm_ostree/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#
# Copyright (C) 2023 Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details. You should have received a copy of the
# GNU General Public License along with this program; if not, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#

import os.path
from pyanaconda.core.path import join_paths

__all__ = ["have_bootupd"]


def have_bootupd(sysroot):
"""Is bootupd/bootupctl present in sysroot?"""
return os.path.exists(join_paths(sysroot, "/usr/bin/bootupctl"))
4 changes: 3 additions & 1 deletion pyanaconda/modules/storage/bootloader/bootloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,9 @@ def install_bootloader_with_tasks(self, payload_type, kernel_versions):
),
InstallBootloaderTask(
storage=self.storage,
mode=self.bootloader_mode
mode=self.bootloader_mode,
payload_type=payload_type,
sysroot=conf.target.system_root
),
CreateBLSEntriesTask(
storage=self.storage,
Expand Down
13 changes: 11 additions & 2 deletions pyanaconda/modules/storage/bootloader/installation.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from blivet import arch
from blivet.devices import BTRFSDevice
from pyanaconda.core.constants import PAYLOAD_TYPE_RPM_OSTREE, PAYLOAD_LIVE_TYPES
from pyanaconda.modules.payloads.payload.rpm_ostree.util import have_bootupd
from pyanaconda.modules.storage.bootloader import BootLoaderError
from pyanaconda.modules.storage.bootloader.systemd import SystemdBoot
from pyanaconda.core.util import execWithRedirect
Expand Down Expand Up @@ -152,11 +153,13 @@ def run(self):
class InstallBootloaderTask(Task):
"""Installation task for the bootloader."""

def __init__(self, storage, mode):
def __init__(self, storage, mode, payload_type, sysroot):
"""Create a new task."""
super().__init__()
self._storage = storage
self._mode = mode
self._payload_type = payload_type
self._sysroot = sysroot

@property
def name(self):
Expand Down Expand Up @@ -185,6 +188,10 @@ def run(self):
log.debug("The bootloader installation is skipped.")
return

if self._payload_type == PAYLOAD_TYPE_RPM_OSTREE and have_bootupd(self._sysroot):
log.debug("Will not install regular bootloader for ostree with bootupd")
return

log.debug("Installing the boot loader.")

try:
Expand Down Expand Up @@ -306,7 +313,9 @@ def run(self):

InstallBootloaderTask(
self._storage,
self._mode
self._mode,
self._payload_type,
self._sysroot
).run()


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
ChangeOSTreeRemoteTask, ConfigureBootloader, DeployOSTreeTask, PullRemoteAndDeleteTask, \
SetSystemRootTask, TearDownOSTreeMountTargetsTask


def _make_config_data():
"""Create OSTree configuration data for testing

Expand Down Expand Up @@ -748,6 +747,47 @@ def test_nonbtrfs_run(self, devdata_mock, storage_mock, symlink_mock, rename_moc
root=sysroot
)

@patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.have_bootupd")
@patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.execWithRedirect")
@patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.os.rename")
@patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.os.symlink")
@patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.STORAGE")
@patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.DeviceData")
def test_bootupd_run(self, devdata_mock, storage_mock, symlink_mock, rename_mock, exec_mock,
have_bootupd_mock):
"""Test OSTree bootloader config task, bootupd"""
exec_mock.return_value = 0
have_bootupd_mock.return_value = True

proxy_mock = storage_mock.get_proxy()
proxy_mock.GetArguments.return_value = ["BOOTLOADER-ARGS"]
proxy_mock.GetFstabSpec.return_value = "FSTAB-SPEC"
proxy_mock.GetRootDevice.return_value = "device-name"
proxy_mock.Drive = "btldr-drv"
devdata_mock.from_structure.return_value.type = "something-non-btrfs-subvolume-ish"
devdata_mock.from_structure.return_value.path = "/dev/btldr-drv"

with tempfile.TemporaryDirectory() as sysroot:
task = ConfigureBootloader(sysroot)
task.run()

rename_mock.assert_not_called()
symlink_mock.assert_not_called()
assert exec_mock.call_count == 2
exec_mock.assert_has_calls([
call(
"bootupctl",
["backend", "install", "--auto", "--with-static-configs", "--device",
"/dev/btldr-drv", "/"],
root=sysroot
),
call(
"ostree",
["admin", "instutil", "set-kargs", "BOOTLOADER-ARGS", "root=FSTAB-SPEC", "rw"],
root=sysroot
)
])

@patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.execWithRedirect")
@patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.os.rename")
@patch("pyanaconda.modules.payloads.payload.rpm_ostree.installation.os.symlink")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#
# Copyright (C) 2020 Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details. You should have received a copy of the
# GNU General Public License along with this program; if not, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
import tempfile
import unittest
from pyanaconda.core.path import join_paths, touch, make_directories

from pyanaconda.modules.payloads.payload.rpm_ostree.util import have_bootupd

class RPMOSTreeUtilTestCase(unittest.TestCase):
"""Test the RPM OSTree utils."""

def test_have_bootupd(self):
"""Test bootupd detection."""
with tempfile.TemporaryDirectory() as sysroot:
assert have_bootupd(sysroot) is False

make_directories(join_paths(sysroot, "/usr/bin"))
touch(join_paths(sysroot, "/usr/bin/bootupctl"))
assert have_bootupd(sysroot) is True
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from tests.unit_tests.pyanaconda_tests import patch_dbus_publish_object, check_dbus_property, \
reset_boot_loader_factory, check_task_creation_list, check_task_creation

from pyanaconda.core.path import make_directories, touch
from pyanaconda.modules.storage import platform
from pyanaconda.modules.storage.bootloader import BootLoaderFactory
from pyanaconda.modules.storage.bootloader.base import BootLoader
Expand All @@ -45,7 +46,7 @@

from pyanaconda.modules.storage.bootloader.image import LinuxBootLoaderImage
from pyanaconda.core.constants import BOOTLOADER_SKIPPED, BOOTLOADER_LOCATION_PARTITION, \
PAYLOAD_TYPE_RPM_OSTREE, PAYLOAD_TYPE_LIVE_IMAGE
PAYLOAD_TYPE_RPM_OSTREE, PAYLOAD_TYPE_LIVE_IMAGE, PAYLOAD_TYPE_DNF
from pyanaconda.modules.common.constants.objects import BOOTLOADER
from pyanaconda.modules.storage.bootloader import BootloaderModule
from pyanaconda.modules.storage.bootloader.bootloader_interface import BootloaderInterface
Expand Down Expand Up @@ -395,15 +396,55 @@ def test_install(self):
bootloader = Mock()
storage = Mock(bootloader=bootloader)

InstallBootloaderTask(storage, BootloaderMode.DISABLED).run()
bootloader.write.assert_not_called()
with tempfile.TemporaryDirectory() as sysroot:
InstallBootloaderTask(
storage,
BootloaderMode.DISABLED,
PAYLOAD_TYPE_DNF,
sysroot
).run()
bootloader.write.assert_not_called()

InstallBootloaderTask(storage, BootloaderMode.SKIPPED).run()
bootloader.write.assert_not_called()
InstallBootloaderTask(
storage,
BootloaderMode.SKIPPED,
PAYLOAD_TYPE_DNF,
sysroot
).run()
bootloader.write.assert_not_called()

InstallBootloaderTask(storage, BootloaderMode.ENABLED).run()
bootloader.prepare.assert_called_once()
bootloader.write.assert_called_once()
InstallBootloaderTask(
storage,
BootloaderMode.ENABLED,
PAYLOAD_TYPE_DNF,
sysroot
).run()
bootloader.prepare.assert_called_once()
bootloader.write.assert_called_once()

bootloader.prepare.reset_mock()
bootloader.write.reset_mock()
InstallBootloaderTask(
storage,
BootloaderMode.ENABLED,
PAYLOAD_TYPE_RPM_OSTREE,
sysroot
).run()
bootloader.prepare.assert_called_once()
bootloader.write.assert_called_once()

bootloader.prepare.reset_mock()
bootloader.write.reset_mock()
make_directories(sysroot + "/usr/bin")
touch(sysroot + "/usr/bin/bootupctl")
InstallBootloaderTask(
storage,
BootloaderMode.ENABLED,
PAYLOAD_TYPE_RPM_OSTREE,
sysroot
).run()
bootloader.prepare.assert_not_called()
bootloader.write.assert_not_called()

@patch('pyanaconda.modules.storage.bootloader.utils.execWithRedirect')
def test_create_bls_entries(self, exec_mock):
Expand Down Expand Up @@ -669,7 +710,9 @@ def test_fix_btrfs(self, configure, install, conf):
)
install.assert_called_once_with(
storage,
BootloaderMode.ENABLED
BootloaderMode.ENABLED,
PAYLOAD_TYPE_LIVE_IMAGE,
sysroot
)

@patch('pyanaconda.modules.storage.bootloader.installation.conf')
Expand Down