Skip to content

Commit

Permalink
Copy dnf.conf to target userspace and allow a custom one
Browse files Browse the repository at this point in the history
This change allows working around the fact that source and target
`dnf.conf` files might be incompatible. For example some of the proxy
configuration between RHEL7 and RHEL8.

Target system compatible configuration can be specified in
/etc/leapp/files/dnf.conf. If this file is present it is copied into
the target userspace and also applied to the target system. If it
doesn't exist, the `/etc/dnf/dnf.conf` from the source system will be
copied instead.

Errors that could be caused by incompatible/incorrect proxy
configuration now contain a hint with a remediation with the steps above
mentioned.

Jira: OAMG-6544
  • Loading branch information
matejmatuska committed Nov 14, 2023
1 parent 60190ff commit 2595961
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 5 deletions.
19 changes: 19 additions & 0 deletions repos/system_upgrade/common/actors/dnfconftargetusecustom/actor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from leapp.actors import Actor
from leapp.libraries.actor import dnfconftargetusecustom
from leapp.tags import ApplicationsPhaseTag, IPUWorkflowTag


class DNFConfTargetUseCustom(Actor):
"""
Move /etc/leapp/files/dnf.conf to /etc/dnf/dnf.conf if it exists
An actor in FactsPhase copies this file to the target userspace if present.
In such case we also want to use the file on the target system.
"""
name = "dnf_conf_target_use_custom"
consumes = ()
produces = ()
tags = (ApplicationsPhaseTag, IPUWorkflowTag)

def process(self):
dnfconftargetusecustom.process()
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import os

from leapp.libraries.stdlib import api, CalledProcessError, run

CUSTOM_DNF_CONF_PATH = "/etc/leapp/files/dnf.conf"


def process():
if os.path.exists(CUSTOM_DNF_CONF_PATH):
try:
run(["mv", CUSTOM_DNF_CONF_PATH, "/etc/dnf/dnf.conf"])
except (CalledProcessError, OSError) as e:
api.current_logger().debug(
"Failed to move /etc/leapp/files/dnf.conf to /etc/dnf/dnf.conf: {}".format(e)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import os

import pytest

from leapp.libraries.actor import dnfconftargetusecustom


@pytest.mark.parametrize(
"exists,should_move",
[(False, False), (True, True)],
)
def test_copy_correct_dnf_conf(monkeypatch, exists, should_move):
monkeypatch.setattr(os.path, "exists", lambda _: exists)

run_called = [False]

def mocked_run(_):
run_called[0] = True

monkeypatch.setattr(dnfconftargetusecustom, 'run', mocked_run)

dnfconftargetusecustom.process()
assert run_called[0] == should_move
24 changes: 24 additions & 0 deletions repos/system_upgrade/common/actors/dnfconfuserspacecopy/actor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from leapp.actors import Actor
from leapp.libraries.actor import dnfconfuserspacecopy
from leapp.models import TargetUserSpacePreupgradeTasks
from leapp.tags import FactsPhaseTag, IPUWorkflowTag


class DNFConfUserspaceCopy(Actor):
"""
Copy dnf.conf to target userspace
Copies /etc/leapp/files/dnf.conf to target userspace. If it isn't available
/etc/dnf/dnf.conf is copied instead. This allows specifying a different
config for the target userspace, which might be required if the source
system configuration file isn't compatible with the target one. One such
example is incompatible proxy configuration between RHEL7 and RHEL8 DNF
versions.
"""
name = "dnf_conf_userspace_copy"
consumes = ()
produces = (TargetUserSpacePreupgradeTasks,)
tags = (FactsPhaseTag, IPUWorkflowTag)

def process(self):
dnfconfuserspacecopy.process()
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import os

from leapp.libraries.stdlib import api
from leapp.models import CopyFile, TargetUserSpacePreupgradeTasks


def process():
src = "/etc/dnf/dnf.conf"
if os.path.exists("/etc/leapp/files/dnf.conf"):
src = "/etc/leapp/files/dnf.conf"

api.current_logger().debug(
"Copying dnf.conf at {} to the target userspace".format(src)
)
api.produce(
TargetUserSpacePreupgradeTasks(
copy_files=[CopyFile(src=src, dst="/etc/dnf/dnf.conf")]
)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import os

import pytest

from leapp.libraries.actor import dnfconfuserspacecopy
from leapp.libraries.common.testutils import logger_mocked, produce_mocked


@pytest.mark.parametrize(
"userspace_conf_exists,expected",
[(False, "/etc/dnf/dnf.conf"), (True, "/etc/leapp/files/dnf.conf")],
)
def test_copy_correct_dnf_conf(monkeypatch, userspace_conf_exists, expected):
monkeypatch.setattr(os.path, "exists", lambda _: userspace_conf_exists)

mocked_produce = produce_mocked()
monkeypatch.setattr(dnfconfuserspacecopy.api, 'produce', mocked_produce)
monkeypatch.setattr(dnfconfuserspacecopy.api, 'current_logger', logger_mocked())

dnfconfuserspacecopy.process()

assert mocked_produce.called == 1
assert len(mocked_produce.model_instances) == 1
assert len(mocked_produce.model_instances[0].copy_files) == 1
assert mocked_produce.model_instances[0].copy_files[0].src == expected
assert mocked_produce.model_instances[0].copy_files[0].dst == "/etc/dnf/dnf.conf"
Original file line number Diff line number Diff line change
Expand Up @@ -269,15 +269,25 @@ def prepare_target_userspace(context, userspace_dir, enabled_repos, packages):
# failed since leapp does not support updates behind proxy yet.
for manager_info in api.consume(PkgManagerInfo):
if manager_info.configured_proxies:
details['details'] = ("DNF failed to install userspace packages, likely due to the proxy "
"configuration detected in the YUM/DNF configuration file.")
details['details'] = (
"DNF failed to install userspace packages, likely due to the proxy "
"configuration detected in the YUM/DNF configuration file. "
"Make sure the proxy is properly configured in /etc/dnf/dnf.conf. "
"It's also possible the proxy settings in the DNF configuration file are "
"incompatible with the target system. A compatible configuration can be "
"placed in /etc/leapp/files/dnf.conf which, if present, will be used during "
"the upgrade instead of /etc/dnf/dnf.conf. "
"In such case the configuration will also be applied to the target system."
)

# Similarly if a proxy was set specifically for one of the repositories.
for repo_facts in api.consume(RepositoriesFacts):
for repo_file in repo_facts.repositories:
if any(repo_data.proxy and repo_data.enabled for repo_data in repo_file.data):
details['details'] = ("DNF failed to install userspace packages, likely due to the proxy "
"configuration detected in a repository configuration file.")
details['details'] = (
"DNF failed to install userspace packages, likely due to the proxy "
"configuration detected in a repository configuration file."
)

raise StopActorExecutionError(message=message, details=details)

Expand Down
22 changes: 21 additions & 1 deletion repos/system_upgrade/common/libraries/dnfplugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,28 @@ def _handle_transaction_err_msg(stage, xfs_info, err, is_container=False):
return # not needed actually as the above function raises error, but for visibility
NO_SPACE_STR = 'more space needed on the'
message = 'DNF execution failed with non zero exit code.'
details = {'STDOUT': err.stdout, 'STDERR': err.stderr}
if NO_SPACE_STR not in err.stderr:
# if there was a problem reaching repos and proxy is configured in DNF/YUM configs, the
# proxy is likely the problem.
# NOTE(mmatuska): We can't consistently detect there was a problem reaching some repos,
# because it isn't clear what are all the possible DNF error messages we can encounter,
# such as: "Failed to synchronize cache for repo ..." or "Errors during downloading
# metadata for # repository" or "No more mirrors to try - All mirrors were already tried
# without success"
# NOTE(mmatuska): We could check PkgManagerInfo to detect if proxy is indeed configured,
# however it would be pretty ugly to pass it all the way down here
proxy_hint = (
"If there was a problem reaching repositories (see stderr output) and proxy is "
"configured in the YUM/DNF configuration file, the proxy configuration is likely "
"causing this error. "
"Make sure the proxy is properly configured in /etc/dnf/dnf.conf. "
"It's also possible the proxy settings in the DNF configuration file are "
"incompatible with the target system. A compatible configuration can be "
"placed in /etc/leapp/files/dnf.conf which, if present, will be used during "
"the upgrade instead of /etc/dnf/dnf.conf. "
"In such case the configuration will also be applied to the target system."
)
details = {'STDOUT': err.stdout, 'STDERR': err.stderr, 'hint': proxy_hint}
raise StopActorExecutionError(message=message, details=details)

# Disk Requirements:
Expand Down

0 comments on commit 2595961

Please sign in to comment.