254 lines
10 KiB
Diff
254 lines
10 KiB
Diff
|
From 7a61c281946ffa0436da8f8837074f17e2103361 Mon Sep 17 00:00:00 2001
|
||
|
From: Matej Matuska <mmatuska@redhat.com>
|
||
|
Date: Wed, 16 Nov 2022 14:11:39 +0100
|
||
|
Subject: [PATCH 27/32] Fix broken or incorrect systemd symlinks
|
||
|
|
||
|
Introduce repairsystemdsymlinks actor.
|
||
|
During the in-place upgrade process, it usually happens that some
|
||
|
symlinks become incorrect - symlinks are broken, or they are defined
|
||
|
in a wrong directory (e.g. when they are supposed to be defined in a
|
||
|
different systemd target). This has various reasons, but usually it's
|
||
|
caused by missing rpm scriptlets in particular rpms.
|
||
|
|
||
|
This change corrects only systemd symlinks are (newly) broken during
|
||
|
the in-place upgrade. Symlinks that have been already broken before
|
||
|
the in-place upgrade are ignored.
|
||
|
|
||
|
Symlinks are handled in the following fashion, if the symlink points to:
|
||
|
- a removed unit, such a symlink is deleted
|
||
|
- a unit whose installation has been changed (e.g. changed WantedBy),
|
||
|
such symlinks are fixed (re-enabled using systemctl)
|
||
|
|
||
|
JIRA:
|
||
|
OAMG-5342
|
||
|
OAMG-5344
|
||
|
OAMG-6519 (possibly related)
|
||
|
OAMG-7755
|
||
|
|
||
|
Bugzillas:
|
||
|
https://bugzilla.redhat.com/show_bug.cgi?id=1988457
|
||
|
https://bugzilla.redhat.com/show_bug.cgi?id=1988449
|
||
|
https://bugzilla.redhat.com/show_bug.cgi?id=2055117 (possibly fixed)
|
||
|
---
|
||
|
.../systemd/repairsystemdsymlinks/actor.py | 25 +++++
|
||
|
.../libraries/repairsystemdsymlinks.py | 76 ++++++++++++++++
|
||
|
.../tests/test_repairsystemdsymlinks.py | 91 +++++++++++++++++++
|
||
|
3 files changed, 192 insertions(+)
|
||
|
create mode 100644 repos/system_upgrade/common/actors/systemd/repairsystemdsymlinks/actor.py
|
||
|
create mode 100644 repos/system_upgrade/common/actors/systemd/repairsystemdsymlinks/libraries/repairsystemdsymlinks.py
|
||
|
create mode 100644 repos/system_upgrade/common/actors/systemd/repairsystemdsymlinks/tests/test_repairsystemdsymlinks.py
|
||
|
|
||
|
diff --git a/repos/system_upgrade/common/actors/systemd/repairsystemdsymlinks/actor.py b/repos/system_upgrade/common/actors/systemd/repairsystemdsymlinks/actor.py
|
||
|
new file mode 100644
|
||
|
index 00000000..29134373
|
||
|
--- /dev/null
|
||
|
+++ b/repos/system_upgrade/common/actors/systemd/repairsystemdsymlinks/actor.py
|
||
|
@@ -0,0 +1,25 @@
|
||
|
+from leapp.actors import Actor
|
||
|
+from leapp.libraries.actor import repairsystemdsymlinks
|
||
|
+from leapp.models import SystemdBrokenSymlinksSource, SystemdBrokenSymlinksTarget, SystemdServicesInfoSource
|
||
|
+from leapp.tags import ApplicationsPhaseTag, IPUWorkflowTag
|
||
|
+
|
||
|
+
|
||
|
+class RepairSystemdSymlinks(Actor):
|
||
|
+ """
|
||
|
+ Fix broken or incorrect systemd symlinks
|
||
|
+
|
||
|
+ Symlinks are handled in the following fashion, if the symlink points to:
|
||
|
+ - a removed unit, such a symlink is deleted
|
||
|
+ - a unit whose installation has been changed (e.g. changed WantedBy),
|
||
|
+ such symlinks are fixed (re-enabled using systemctl)
|
||
|
+
|
||
|
+ Symlinks that have been already broken before the in-place upgrade are ignored.
|
||
|
+ """
|
||
|
+
|
||
|
+ name = 'repair_systemd_symlinks'
|
||
|
+ consumes = (SystemdBrokenSymlinksSource, SystemdBrokenSymlinksTarget, SystemdServicesInfoSource)
|
||
|
+ produces = ()
|
||
|
+ tags = (ApplicationsPhaseTag, IPUWorkflowTag)
|
||
|
+
|
||
|
+ def process(self):
|
||
|
+ repairsystemdsymlinks.process()
|
||
|
diff --git a/repos/system_upgrade/common/actors/systemd/repairsystemdsymlinks/libraries/repairsystemdsymlinks.py b/repos/system_upgrade/common/actors/systemd/repairsystemdsymlinks/libraries/repairsystemdsymlinks.py
|
||
|
new file mode 100644
|
||
|
index 00000000..884b001e
|
||
|
--- /dev/null
|
||
|
+++ b/repos/system_upgrade/common/actors/systemd/repairsystemdsymlinks/libraries/repairsystemdsymlinks.py
|
||
|
@@ -0,0 +1,76 @@
|
||
|
+import os
|
||
|
+
|
||
|
+from leapp.exceptions import StopActorExecutionError
|
||
|
+from leapp.libraries.common import systemd
|
||
|
+from leapp.libraries.common.config.version import get_target_major_version
|
||
|
+from leapp.libraries.stdlib import api, CalledProcessError, run
|
||
|
+from leapp.models import SystemdBrokenSymlinksSource, SystemdBrokenSymlinksTarget, SystemdServicesInfoSource
|
||
|
+
|
||
|
+_INSTALLATION_CHANGED_EL8 = ['rngd.service', 'sysstat.service']
|
||
|
+_INSTALLATION_CHANGED_EL9 = []
|
||
|
+
|
||
|
+
|
||
|
+def _get_installation_changed_units():
|
||
|
+ version = get_target_major_version()
|
||
|
+ if version == '8':
|
||
|
+ return _INSTALLATION_CHANGED_EL8
|
||
|
+ if version == '9':
|
||
|
+ return _INSTALLATION_CHANGED_EL9
|
||
|
+
|
||
|
+ return []
|
||
|
+
|
||
|
+
|
||
|
+def _service_enabled_source(service_info, name):
|
||
|
+ service_file = next((s for s in service_info.service_files if s.name == name), None)
|
||
|
+ return service_file and service_file.state == 'enabled'
|
||
|
+
|
||
|
+
|
||
|
+def _is_unit_enabled(unit):
|
||
|
+ try:
|
||
|
+ ret = run(['systemctl', 'is-enabled', unit], split=True)['stdout']
|
||
|
+ return ret and ret[0] == 'enabled'
|
||
|
+ except (OSError, CalledProcessError):
|
||
|
+ return False
|
||
|
+
|
||
|
+
|
||
|
+def _handle_newly_broken_symlinks(symlinks, service_info):
|
||
|
+ for symlink in symlinks:
|
||
|
+ unit = os.path.basename(symlink)
|
||
|
+ try:
|
||
|
+ if not _is_unit_enabled(unit):
|
||
|
+ # removes the broken symlink
|
||
|
+ systemd.disable_unit(unit)
|
||
|
+ elif _service_enabled_source(service_info, unit) and _is_unit_enabled(unit):
|
||
|
+ # removes the old symlinks and creates the new ones
|
||
|
+ systemd.reenable_unit(unit)
|
||
|
+ except CalledProcessError:
|
||
|
+ # TODO(mmatuska): Produce post-upgrade report: failed to handle broken symlink (and suggest a fix?)
|
||
|
+ pass
|
||
|
+
|
||
|
+
|
||
|
+def _handle_bad_symlinks(service_files):
|
||
|
+ install_changed_units = _get_installation_changed_units()
|
||
|
+ potentially_bad = [s for s in service_files if s.name in install_changed_units]
|
||
|
+
|
||
|
+ for unit_file in potentially_bad:
|
||
|
+ if unit_file.state == 'enabled' and _is_unit_enabled(unit_file.name):
|
||
|
+ systemd.reenable_unit(unit_file.name)
|
||
|
+
|
||
|
+
|
||
|
+def process():
|
||
|
+ service_info_source = next(api.consume(SystemdServicesInfoSource), None)
|
||
|
+ if not service_info_source:
|
||
|
+ raise StopActorExecutionError("Expected SystemdServicesInfoSource message, but got None")
|
||
|
+
|
||
|
+ source_info = next(api.consume(SystemdBrokenSymlinksSource), None)
|
||
|
+ target_info = next(api.consume(SystemdBrokenSymlinksTarget), None)
|
||
|
+
|
||
|
+ if source_info and target_info:
|
||
|
+ newly_broken = []
|
||
|
+ newly_broken = [s for s in target_info.broken_symlinks if s not in source_info.broken_symlinks]
|
||
|
+ if not newly_broken:
|
||
|
+ return
|
||
|
+
|
||
|
+ _handle_newly_broken_symlinks(newly_broken, service_info_source)
|
||
|
+
|
||
|
+ _handle_bad_symlinks(service_info_source.service_files)
|
||
|
diff --git a/repos/system_upgrade/common/actors/systemd/repairsystemdsymlinks/tests/test_repairsystemdsymlinks.py b/repos/system_upgrade/common/actors/systemd/repairsystemdsymlinks/tests/test_repairsystemdsymlinks.py
|
||
|
new file mode 100644
|
||
|
index 00000000..2394df5e
|
||
|
--- /dev/null
|
||
|
+++ b/repos/system_upgrade/common/actors/systemd/repairsystemdsymlinks/tests/test_repairsystemdsymlinks.py
|
||
|
@@ -0,0 +1,91 @@
|
||
|
+from leapp.libraries.actor import repairsystemdsymlinks
|
||
|
+from leapp.libraries.common import systemd
|
||
|
+from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked
|
||
|
+from leapp.libraries.stdlib import api, CalledProcessError, run
|
||
|
+from leapp.models import (
|
||
|
+ SystemdBrokenSymlinksSource,
|
||
|
+ SystemdBrokenSymlinksTarget,
|
||
|
+ SystemdServiceFile,
|
||
|
+ SystemdServicesInfoSource
|
||
|
+)
|
||
|
+
|
||
|
+
|
||
|
+class MockedSystemdCmd(object):
|
||
|
+ def __init__(self):
|
||
|
+ self.units = []
|
||
|
+
|
||
|
+ def __call__(self, unit, *args, **kwargs):
|
||
|
+ self.units.append(unit)
|
||
|
+ return {}
|
||
|
+
|
||
|
+
|
||
|
+def test_bad_symslinks(monkeypatch):
|
||
|
+ service_files = [
|
||
|
+ SystemdServiceFile(name='rngd.service', state='enabled'),
|
||
|
+ SystemdServiceFile(name='sysstat.service', state='disabled'),
|
||
|
+ SystemdServiceFile(name='hello.service', state='enabled'),
|
||
|
+ SystemdServiceFile(name='world.service', state='disabled'),
|
||
|
+ ]
|
||
|
+
|
||
|
+ def is_unit_enabled_mocked(unit):
|
||
|
+ return True
|
||
|
+
|
||
|
+ monkeypatch.setattr(repairsystemdsymlinks, '_is_unit_enabled', is_unit_enabled_mocked)
|
||
|
+
|
||
|
+ reenable_mocked = MockedSystemdCmd()
|
||
|
+ monkeypatch.setattr(systemd, 'reenable_unit', reenable_mocked)
|
||
|
+
|
||
|
+ service_info = SystemdServicesInfoSource(service_files=service_files)
|
||
|
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[service_info]))
|
||
|
+
|
||
|
+ repairsystemdsymlinks._handle_bad_symlinks(service_info.service_files)
|
||
|
+
|
||
|
+ assert reenable_mocked.units == ['rngd.service']
|
||
|
+
|
||
|
+
|
||
|
+def test_handle_newly_broken_symlink(monkeypatch):
|
||
|
+
|
||
|
+ symlinks = [
|
||
|
+ '/etc/systemd/system/default.target.wants/systemd-readahead-replay.service',
|
||
|
+ '/etc/systemd/system/multi-user.target.wants/vdo.service',
|
||
|
+ '/etc/systemd/system/multi-user.target.wants/hello.service',
|
||
|
+ '/etc/systemd/system/multi-user.target.wants/world.service',
|
||
|
+ '/etc/systemd/system/multi-user.target.wants/foo.service',
|
||
|
+ '/etc/systemd/system/multi-user.target.wants/bar.service',
|
||
|
+ ]
|
||
|
+
|
||
|
+ def is_unit_enabled_mocked(unit):
|
||
|
+ return unit in ('hello.service', 'foo.service')
|
||
|
+
|
||
|
+ expect_disabled = [
|
||
|
+ 'systemd-readahead-replay.service',
|
||
|
+ 'vdo.service',
|
||
|
+ 'world.service',
|
||
|
+ 'bar.service',
|
||
|
+ ]
|
||
|
+
|
||
|
+ expect_reenabled = [
|
||
|
+ 'hello.service',
|
||
|
+ ]
|
||
|
+
|
||
|
+ monkeypatch.setattr(repairsystemdsymlinks, '_is_unit_enabled', is_unit_enabled_mocked)
|
||
|
+
|
||
|
+ reenable_mocked = MockedSystemdCmd()
|
||
|
+ monkeypatch.setattr(systemd, 'reenable_unit', reenable_mocked)
|
||
|
+
|
||
|
+ disable_mocked = MockedSystemdCmd()
|
||
|
+ monkeypatch.setattr(systemd, 'disable_unit', disable_mocked)
|
||
|
+
|
||
|
+ service_files = [
|
||
|
+ SystemdServiceFile(name='systemd-readahead-replay.service', state='enabled'),
|
||
|
+ SystemdServiceFile(name='vdo.service', state='disabled'),
|
||
|
+ SystemdServiceFile(name='hello.service', state='enabled'),
|
||
|
+ SystemdServiceFile(name='world.service', state='disabled'),
|
||
|
+ SystemdServiceFile(name='foo.service', state='disabled'),
|
||
|
+ SystemdServiceFile(name='bar.service', state='enabled'),
|
||
|
+ ]
|
||
|
+ service_info = SystemdServicesInfoSource(service_files=service_files)
|
||
|
+ repairsystemdsymlinks._handle_newly_broken_symlinks(symlinks, service_info)
|
||
|
+
|
||
|
+ assert reenable_mocked.units == expect_reenabled
|
||
|
+ assert disable_mocked.units == expect_disabled
|
||
|
--
|
||
|
2.38.1
|
||
|
|