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

Support DNF5's config-manager #5657

Merged
merged 1 commit into from
Aug 20, 2024
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
2 changes: 2 additions & 0 deletions pyanaconda/modules/payloads/payload/dnf/dnf.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ def install_with_tasks(self):
ResolvePackagesTask(
dnf_manager=self.dnf_manager,
selection=self.packages_selection,
configuration=self.packages_configuration,
),
PrepareDownloadLocationTask(
dnf_manager=self.dnf_manager,
Expand Down Expand Up @@ -472,6 +473,7 @@ def post_install_with_tasks(self):
UpdateDNFConfigurationTask(
sysroot=conf.target.system_root,
configuration=self.packages_configuration,
dnf_manager=self.dnf_manager,
),
ResetDNFManagerTask(
dnf_manager=self.dnf_manager
Expand Down
38 changes: 30 additions & 8 deletions pyanaconda/modules/payloads/payload/dnf/installation.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@
from pyanaconda.anaconda_loggers import get_module_logger
from pyanaconda.core import util
from pyanaconda.core.configuration.anaconda import conf
from pyanaconda.core.constants import RPM_LANGUAGES_NONE, RPM_LANGUAGES_ALL, MULTILIB_POLICY_BEST
from pyanaconda.core.constants import RPM_LANGUAGES_NONE, RPM_LANGUAGES_ALL, \
MULTILIB_POLICY_BEST
from pyanaconda.core.i18n import _
from pyanaconda.core.path import join_paths, make_directories
from pyanaconda.modules.common.errors.installation import PayloadInstallationError, \
NonCriticalInstallationError
from pyanaconda.modules.common.structures.packages import PackagesConfigurationData
from pyanaconda.modules.common.task import Task
from pyanaconda.modules.payloads.payload.dnf.requirements import collect_remote_requirements, \
collect_language_requirements, collect_platform_requirements, \
collect_language_requirements, collect_platform_requirements, collect_dnf_requirements, \
collect_driver_disk_requirements, apply_requirements
from pyanaconda.modules.payloads.payload.dnf.utils import pick_download_location, \
get_kernel_version_list
Expand Down Expand Up @@ -100,6 +101,18 @@ def _install_macros(self, macros):
class ResolvePackagesTask(CheckPackagesSelectionTask):
"""Installation task to resolve the software selection."""

def __init__(self, dnf_manager, selection, configuration):
"""Resolve packages task
:param dnf_manager: a DNF manager
:param selection: a package selection data
:param configuration: a packages configuration data
"""
super().__init__(dnf_manager, selection)
self._dnf_manager = dnf_manager
self._selection = selection
self._configuration = configuration

@property
def name(self):
"""The name of the task."""
Expand Down Expand Up @@ -132,6 +145,7 @@ def _requirements(self):
return collect_remote_requirements() \
+ collect_language_requirements(self._dnf_manager) \
+ collect_platform_requirements(self._dnf_manager) \
+ collect_dnf_requirements(self._dnf_manager, self._configuration) \
+ collect_driver_disk_requirements()

def _collect_required_specs(self):
Expand Down Expand Up @@ -377,7 +391,7 @@ def run(self):
class UpdateDNFConfigurationTask(Task):
"""The installation task to update the dnf.conf file."""

def __init__(self, sysroot, configuration: PackagesConfigurationData):
def __init__(self, sysroot, configuration: PackagesConfigurationData, dnf_manager):
"""Create a new task.
:param sysroot: a path to the system root
Expand All @@ -386,6 +400,7 @@ def __init__(self, sysroot, configuration: PackagesConfigurationData):
super().__init__()
self._sysroot = sysroot
self._data = configuration
self._dnf_manager = dnf_manager

@property
def name(self):
Expand All @@ -405,11 +420,18 @@ def _set_option(self, option, value):
log.debug("Setting '%s' to '%s'.", option, value)

cmd = "dnf"
args = [
"config-manager",
"--save",
"--setopt={}={}".format(option, value),
]
if self._dnf_manager.is_package_available("dnf5"):
args = [
"config-manager",
"setopt",
"{}={}".format(option, value)
]
else:
args = [
"config-manager",
"--save",
"--setopt={}={}".format(option, value)
]

try:
rc = util.execWithRedirect(cmd, args, root=self._sysroot)
Expand Down
26 changes: 25 additions & 1 deletion pyanaconda/modules/payloads/payload/dnf/requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@

from pyanaconda.anaconda_loggers import get_module_logger
from pyanaconda.core.configuration.anaconda import conf
from pyanaconda.core.constants import REQUIREMENT_TYPE_PACKAGE, REQUIREMENT_TYPE_GROUP
from pyanaconda.core.constants import REQUIREMENT_TYPE_PACKAGE, REQUIREMENT_TYPE_GROUP, \
MULTILIB_POLICY_BEST
from pyanaconda.core.hw import detect_virtualized_platform
from pyanaconda.localization import find_best_locale_match, is_valid_langcode
from pyanaconda.modules.common.constants.services import LOCALIZATION, BOSS
Expand Down Expand Up @@ -80,6 +81,29 @@ def collect_language_requirements(dnf_manager):
return requirements


def collect_dnf_requirements(dnf_manager, packages_configuration):
"""Collect the requirements for the current dnf.
:param dnf_manager: a DNF manager
:param package_configuration: packages selection
:return: a list of requirements
"""
requirements = []

# Detect if dnf plugin is required
if dnf_manager.is_package_available("dnf5"):
plugins_name = "dnf5-modules"
else:
plugins_name = "dnf-plugins-core"

if packages_configuration.multilib_policy != MULTILIB_POLICY_BEST:
requirements.append(
Requirement.for_package(plugins_name, reason="Needed to enable multilib support.")
)

return requirements


def collect_platform_requirements(dnf_manager):
"""Collect the requirements for the current platform.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,8 +374,9 @@ def test_resolve(self, kernel_getter, req_getter1, req_getter2, req_getter3, req

dnf_manager = Mock()
dnf_manager.default_environment = None
data = PackagesConfigurationData()

task = ResolvePackagesTask(dnf_manager, selection)
task = ResolvePackagesTask(dnf_manager, selection, data)
task.run()

dnf_manager.clear_selection.assert_called_once_with()
Expand Down Expand Up @@ -408,7 +409,8 @@ def test_fail(self, kernel_getter, req_getter1, req_getter2, req_getter3, req_ge
dnf_manager.apply_specs.side_effect = MissingSpecsError("e2")

with pytest.raises(NonCriticalInstallationError) as cm:
task = ResolvePackagesTask(dnf_manager, selection)
data = PackagesConfigurationData()
task = ResolvePackagesTask(dnf_manager, selection, data)
task.run()

expected = "e1\n\ne2"
Expand All @@ -418,7 +420,8 @@ def test_fail(self, kernel_getter, req_getter1, req_getter2, req_getter3, req_ge
dnf_manager.resolve_selection.side_effect = InvalidSelectionError("e4")

with pytest.raises(PayloadInstallationError) as cm:
task = ResolvePackagesTask(dnf_manager, selection)
data = PackagesConfigurationData()
task = ResolvePackagesTask(dnf_manager, selection, data)
task.run()

expected = "e3\n\ne4"
Expand All @@ -433,8 +436,9 @@ def test_no_update(self, execute):
"""Don't update the DNF configuration."""
with tempfile.TemporaryDirectory() as sysroot:
data = PackagesConfigurationData()
dnf_manager = DNFManager()

task = UpdateDNFConfigurationTask(sysroot, data)
task = UpdateDNFConfigurationTask(sysroot, data, dnf_manager)
task.run()

execute.assert_not_called()
Expand All @@ -447,8 +451,9 @@ def test_failed_update(self, execute):
with tempfile.TemporaryDirectory() as sysroot:
data = PackagesConfigurationData()
data.multilib_policy = MULTILIB_POLICY_ALL
dnf_manager = DNFManager()

task = UpdateDNFConfigurationTask(sysroot, data)
task = UpdateDNFConfigurationTask(sysroot, data, dnf_manager)

with self.assertLogs(level="WARNING") as cm:
task.run()
Expand All @@ -464,8 +469,9 @@ def test_error_update(self, execute):
with tempfile.TemporaryDirectory() as sysroot:
data = PackagesConfigurationData()
data.multilib_policy = MULTILIB_POLICY_ALL
dnf_manager = DNFManager()

task = UpdateDNFConfigurationTask(sysroot, data)
task = UpdateDNFConfigurationTask(sysroot, data, dnf_manager)

with self.assertLogs(level="WARNING") as cm:
task.run()
Expand All @@ -474,15 +480,17 @@ def test_error_update(self, execute):
assert any(map(lambda x: msg in x, cm.output))

@patch("pyanaconda.core.util.execWithRedirect")
def test_multilib_policy(self, execute):
"""Update the multilib policy."""
def test_multilib_policy_dnf4(self, execute):
"""Update the multilib policy on pre-dnf5 systems."""
execute.return_value = 0

with tempfile.TemporaryDirectory() as sysroot:
data = PackagesConfigurationData()
data.multilib_policy = MULTILIB_POLICY_ALL
dnf_manager = Mock(spec=DNFManager)
dnf_manager.is_package_available.return_value = False

task = UpdateDNFConfigurationTask(sysroot, data)
task = UpdateDNFConfigurationTask(sysroot, data, dnf_manager)
task.run()
Comment on lines +483 to 494
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be great to check also if the required plugin package is correctly requested.


execute.assert_called_once_with(
Expand All @@ -495,6 +503,30 @@ def test_multilib_policy(self, execute):
root=sysroot
)

@patch("pyanaconda.core.util.execWithRedirect")
def test_multilib_policy_dnf5(self, execute):
"""Update the multilib policy on dnf5 systems."""
execute.return_value = 0

with tempfile.TemporaryDirectory() as sysroot:
data = PackagesConfigurationData()
data.multilib_policy = MULTILIB_POLICY_ALL
dnf_manager = Mock(spec=DNFManager)
dnf_manager.is_package_available.return_value = True

task = UpdateDNFConfigurationTask(sysroot, data, dnf_manager)
task.run()

execute.assert_called_once_with(
"dnf",
[
"config-manager",
"setopt",
"multilib_policy=all",
],
root=sysroot
)


class WriteRepositoriesTaskTestCase(unittest.TestCase):
"""Test the WriteRepositoriesTask task."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@
import unittest
from unittest.mock import Mock, patch

from pyanaconda.core.constants import REQUIREMENT_TYPE_PACKAGE, REQUIREMENT_TYPE_GROUP
from pyanaconda.core.constants import REQUIREMENT_TYPE_PACKAGE, REQUIREMENT_TYPE_GROUP, \
MULTILIB_POLICY_ALL, MULTILIB_POLICY_BEST
from pyanaconda.modules.common.constants.services import LOCALIZATION, BOSS
from pyanaconda.modules.common.structures.requirement import Requirement
from pyanaconda.modules.payloads.payload.dnf.dnf_manager import DNFManager
from pyanaconda.modules.payloads.payload.dnf.requirements import collect_language_requirements, \
collect_platform_requirements, collect_driver_disk_requirements, collect_remote_requirements, \
apply_requirements
apply_requirements, collect_dnf_requirements
from tests.unit_tests.pyanaconda_tests import patch_dbus_get_proxy_with_cache
from pyanaconda.modules.common.structures.packages import PackagesConfigurationData


class DNFRequirementsTestCase(unittest.TestCase):
Expand Down Expand Up @@ -118,6 +120,40 @@ def test_collect_platform_requirements(self, execute):

self._compare_requirements(requirements, [r1])

@patch('pyanaconda.core.hw.execWithCapture')
def test_collect_dnf_requirements(self, execute):
"""Test the function collect_dnf_requirements."""

data = PackagesConfigurationData()
data.multilib_policy = MULTILIB_POLICY_BEST
dnf_manager = Mock(spec=DNFManager)
dnf_manager.is_package_available.return_value = True

# No need for dnf config
execute.return_value = None
requirements = collect_dnf_requirements(dnf_manager, data)
assert requirements == []

data.multilib_policy = MULTILIB_POLICY_ALL

# Require dnf-5 version of plugins
dnf_manager.is_package_available.return_value = True
requirements = collect_dnf_requirements(dnf_manager, data)
r = self._create_requirement(
name="dnf5-modules",
reason="Needed to enable multilib support."
)
self._compare_requirements(requirements, [r])

# Require dnf-4 version of plugins
dnf_manager.is_package_available.return_value = False
requirements = collect_dnf_requirements(dnf_manager, data)
r = self._create_requirement(
name="dnf-plugins-core",
reason="Needed to enable multilib support."
)
self._compare_requirements(requirements, [r])

def test_collect_driver_disk_requirements(self):
"""Test the function collect_driver_disk_requirements."""
requirements = collect_driver_disk_requirements("/non/existent/file")
Expand Down