272 lines
11 KiB
Diff
272 lines
11 KiB
Diff
From 2713d60a99b60a352b89374dec89f6faa683861d Mon Sep 17 00:00:00 2001
|
|
From: Matej Matuska <mmatuska@redhat.com>
|
|
Date: Wed, 16 Nov 2022 14:19:36 +0100
|
|
Subject: [PATCH 28/32] Add check for systemd symlinks broken before the
|
|
upgrade
|
|
|
|
Broken systemd symlinks are not treated during the in-place upgrade
|
|
if they are broken prior the leapp execution. This could lead in
|
|
unwanted behaviour on the upgraded system, but it does not have to
|
|
- so we do not inhibit the upgrade when such symlinks are detected.
|
|
|
|
Also, such symlinks could have been created by previous in-place
|
|
upgrade, when an automatical fixing of broken symlinks have not been
|
|
implemented yet. By this actor we inform people about such issues,
|
|
so they can fix it prior the upgrade.
|
|
|
|
Co-authored-by: Petr Stodulka <pstodulk@redhat.com>
|
|
---
|
|
.../checksystemdbrokensymlinks/actor.py | 29 +++++
|
|
.../libraries/checksystemdbrokensymlinks.py | 106 ++++++++++++++++++
|
|
.../tests/test_checksystemdbrokensymlinks.py | 89 +++++++++++++++
|
|
3 files changed, 224 insertions(+)
|
|
create mode 100644 repos/system_upgrade/common/actors/systemd/checksystemdbrokensymlinks/actor.py
|
|
create mode 100644 repos/system_upgrade/common/actors/systemd/checksystemdbrokensymlinks/libraries/checksystemdbrokensymlinks.py
|
|
create mode 100644 repos/system_upgrade/common/actors/systemd/checksystemdbrokensymlinks/tests/test_checksystemdbrokensymlinks.py
|
|
|
|
diff --git a/repos/system_upgrade/common/actors/systemd/checksystemdbrokensymlinks/actor.py b/repos/system_upgrade/common/actors/systemd/checksystemdbrokensymlinks/actor.py
|
|
new file mode 100644
|
|
index 00000000..257e8c33
|
|
--- /dev/null
|
|
+++ b/repos/system_upgrade/common/actors/systemd/checksystemdbrokensymlinks/actor.py
|
|
@@ -0,0 +1,29 @@
|
|
+from leapp.actors import Actor
|
|
+from leapp.libraries.actor import checksystemdbrokensymlinks
|
|
+from leapp.models import SystemdBrokenSymlinksSource, SystemdServicesInfoSource
|
|
+from leapp.reporting import Report
|
|
+from leapp.tags import ChecksPhaseTag, IPUWorkflowTag
|
|
+
|
|
+
|
|
+class CheckSystemdBrokenSymlinks(Actor):
|
|
+ """
|
|
+ Check whether some systemd symlinks are broken
|
|
+
|
|
+ If some systemd symlinks are broken, report them but do not inhibit the
|
|
+ upgrade. The symlinks broken already before the upgrade will not be
|
|
+ handled by the upgrade process anyhow. Two different reports are created:
|
|
+ - symlinks which have the same filename as an existing enabled systemd
|
|
+ service (the symlink doesn't point to an existing unit file, but the
|
|
+ service is enabled)
|
|
+ - broken symlinks which names do not correspond with any existing systemd
|
|
+ unit file (typically when the service is removed but not disabled
|
|
+ correctly)
|
|
+ """
|
|
+
|
|
+ name = 'check_systemd_broken_symlinks'
|
|
+ consumes = (SystemdBrokenSymlinksSource, SystemdServicesInfoSource)
|
|
+ produces = (Report,)
|
|
+ tags = (ChecksPhaseTag, IPUWorkflowTag)
|
|
+
|
|
+ def process(self):
|
|
+ checksystemdbrokensymlinks.process()
|
|
diff --git a/repos/system_upgrade/common/actors/systemd/checksystemdbrokensymlinks/libraries/checksystemdbrokensymlinks.py b/repos/system_upgrade/common/actors/systemd/checksystemdbrokensymlinks/libraries/checksystemdbrokensymlinks.py
|
|
new file mode 100644
|
|
index 00000000..23addf72
|
|
--- /dev/null
|
|
+++ b/repos/system_upgrade/common/actors/systemd/checksystemdbrokensymlinks/libraries/checksystemdbrokensymlinks.py
|
|
@@ -0,0 +1,106 @@
|
|
+import os
|
|
+
|
|
+from leapp import reporting
|
|
+from leapp.exceptions import StopActorExecutionError
|
|
+from leapp.libraries.stdlib import api
|
|
+from leapp.models import SystemdBrokenSymlinksSource, SystemdServicesInfoSource
|
|
+
|
|
+FMT_LIST_SEPARATOR = '\n - '
|
|
+
|
|
+
|
|
+def _report_broken_symlinks(symlinks):
|
|
+ summary = (
|
|
+ 'Leapp detected broken systemd symlinks on the system that do not'
|
|
+ ' correspond to any installed systemd unit.'
|
|
+ ' This typically happens when the original systemd unit file has been'
|
|
+ ' removed (e.g. an rpm removal) or renamed and the system configration'
|
|
+ ' has not been properly modified.'
|
|
+ ' These symlinks will not be handled during the in-place upgrade'
|
|
+ ' as they are already broken.'
|
|
+ ' The list of detected broken systemd symlinks:{}{}'
|
|
+ .format(FMT_LIST_SEPARATOR, FMT_LIST_SEPARATOR.join(sorted(symlinks)))
|
|
+ )
|
|
+
|
|
+ command = ['/usr/bin/rm'] + symlinks
|
|
+
|
|
+ hint = (
|
|
+ 'Remove the invalid symlinks before the upgrade.'
|
|
+ )
|
|
+
|
|
+ reporting.create_report([
|
|
+ reporting.Title(
|
|
+ 'Detected broken systemd symlinks for non-existing services'
|
|
+ ),
|
|
+ reporting.Summary(summary),
|
|
+ reporting.Remediation(hint=hint, commands=[command]),
|
|
+ reporting.Severity(reporting.Severity.LOW),
|
|
+ reporting.Tags([reporting.Tags.FILESYSTEM]),
|
|
+ ])
|
|
+
|
|
+
|
|
+def _report_enabled_services_broken_symlinks(symlinks):
|
|
+ summary = (
|
|
+ 'Leapp detected broken systemd symlinks on the system that correspond'
|
|
+ ' to existing systemd units, but on different paths. This could lead'
|
|
+ ' in future to unexpected behaviour. Also, these symlinks will not be'
|
|
+ ' handled during the in-place upgrade as they are already broken.'
|
|
+ ' The list of detected broken symlinks:{}{}'
|
|
+ .format(FMT_LIST_SEPARATOR, FMT_LIST_SEPARATOR.join(sorted(symlinks)))
|
|
+ )
|
|
+
|
|
+ hint = (
|
|
+ 'Fix the broken symlinks before the upgrade or remove them. For this'
|
|
+ ' purpose, you can re-enable or disable the related systemd services'
|
|
+ ' using the systemctl tool.'
|
|
+ )
|
|
+
|
|
+ reporting.create_report([
|
|
+ reporting.Title(
|
|
+ 'Detected broken systemd symlinks for existing services'
|
|
+ ),
|
|
+ reporting.Summary(summary),
|
|
+ reporting.Remediation(hint=hint),
|
|
+ reporting.Severity(reporting.Severity.MEDIUM),
|
|
+ reporting.Tags([reporting.Tags.FILESYSTEM]),
|
|
+ ])
|
|
+
|
|
+
|
|
+def _is_enabled(unit, service_files):
|
|
+ # FIXME(pstodulk): currently our msgs contain only information about systemd
|
|
+ # services. If the unit (broken symlink) refers to timers, etc. They will
|
|
+ # be treated now as disabled (read: symlink is broken and there is not
|
|
+ # a corresponding unit-file on the system). Considering it for now as
|
|
+ # minor issue that will be resolved in future.
|
|
+ # NOTE: One of possible solution is to put the information about enabled broken
|
|
+ # symlinks to the msg, so it can be just consumed.
|
|
+ for service_file in service_files:
|
|
+ if service_file.name == unit:
|
|
+ return service_file.state == 'enabled'
|
|
+ return False
|
|
+
|
|
+
|
|
+def process():
|
|
+ broken_symlinks_info = next(api.consume(SystemdBrokenSymlinksSource), None)
|
|
+ if not broken_symlinks_info:
|
|
+ # nothing to do
|
|
+ return
|
|
+ services = next(api.consume(SystemdServicesInfoSource), None)
|
|
+ if not services:
|
|
+ # This is just a seatbelt. It's not expected this msg will be missing.
|
|
+ # Skipping tests.
|
|
+ raise StopActorExecutionError('Missing SystemdServicesInfoSource message.')
|
|
+
|
|
+ enabled_to_report = []
|
|
+ to_report = []
|
|
+ for broken_symlink in broken_symlinks_info.broken_symlinks:
|
|
+ unit = os.path.basename(broken_symlink)
|
|
+ if _is_enabled(unit, services.service_files):
|
|
+ enabled_to_report.append(broken_symlink)
|
|
+ else:
|
|
+ to_report.append(broken_symlink)
|
|
+
|
|
+ if enabled_to_report:
|
|
+ _report_enabled_services_broken_symlinks(enabled_to_report)
|
|
+
|
|
+ if to_report:
|
|
+ _report_broken_symlinks(to_report)
|
|
diff --git a/repos/system_upgrade/common/actors/systemd/checksystemdbrokensymlinks/tests/test_checksystemdbrokensymlinks.py b/repos/system_upgrade/common/actors/systemd/checksystemdbrokensymlinks/tests/test_checksystemdbrokensymlinks.py
|
|
new file mode 100644
|
|
index 00000000..2364f7a5
|
|
--- /dev/null
|
|
+++ b/repos/system_upgrade/common/actors/systemd/checksystemdbrokensymlinks/tests/test_checksystemdbrokensymlinks.py
|
|
@@ -0,0 +1,89 @@
|
|
+import pytest
|
|
+
|
|
+from leapp import reporting
|
|
+from leapp.libraries.actor import checksystemdbrokensymlinks
|
|
+from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked
|
|
+from leapp.libraries.stdlib import api
|
|
+from leapp.models import SystemdBrokenSymlinksSource, SystemdServiceFile, SystemdServicesInfoSource
|
|
+
|
|
+
|
|
+def test_report_broken_symlinks(monkeypatch):
|
|
+
|
|
+ symlinks = [
|
|
+ '/etc/systemd/system/multi-user.target.wants/hello.service',
|
|
+ '/etc/systemd/system/multi-user.target.wants/world.service',
|
|
+ ]
|
|
+
|
|
+ created_reports = create_report_mocked()
|
|
+ monkeypatch.setattr(reporting, 'create_report', created_reports)
|
|
+
|
|
+ checksystemdbrokensymlinks._report_broken_symlinks(symlinks)
|
|
+
|
|
+ assert created_reports.called
|
|
+ assert all([s in created_reports.report_fields['summary'] for s in symlinks])
|
|
+
|
|
+
|
|
+def test_report_enabled_services_broken_symlinks(monkeypatch):
|
|
+ symlinks = [
|
|
+ '/etc/systemd/system/multi-user.target.wants/foo.service',
|
|
+ '/etc/systemd/system/multi-user.target.wants/bar.service',
|
|
+ ]
|
|
+
|
|
+ created_reports = create_report_mocked()
|
|
+ monkeypatch.setattr(reporting, 'create_report', created_reports)
|
|
+
|
|
+ checksystemdbrokensymlinks._report_enabled_services_broken_symlinks(symlinks)
|
|
+
|
|
+ assert created_reports.called
|
|
+ assert all([s in created_reports.report_fields['summary'] for s in symlinks])
|
|
+
|
|
+
|
|
+class ReportBrokenSymlinks(object):
|
|
+ def __init__(self):
|
|
+ self.symlinks = []
|
|
+
|
|
+ def __call__(self, unit, *args, **kwargs):
|
|
+ self.symlinks.append(unit)
|
|
+ return {}
|
|
+
|
|
+
|
|
+def test_broken_symlinks_reported(monkeypatch):
|
|
+ broken_symlinks = SystemdBrokenSymlinksSource(broken_symlinks=[
|
|
+ '/etc/systemd/system/multi-user.target.wants/foo.service',
|
|
+ '/etc/systemd/system/multi-user.target.wants/bar.service',
|
|
+ '/etc/systemd/system/multi-user.target.wants/hello.service',
|
|
+ '/etc/systemd/system/multi-user.target.wants/world.service',
|
|
+ ])
|
|
+ systemd_services = SystemdServicesInfoSource(service_files=[
|
|
+ SystemdServiceFile(name='foo.service', state='enabled'),
|
|
+ SystemdServiceFile(name='bar.service', state='enabled'),
|
|
+ SystemdServiceFile(name='hello.service', state='disabled'),
|
|
+ ])
|
|
+ broken = []
|
|
+ enabled_broken = []
|
|
+
|
|
+ def _report_broken_symlinks_mocked(symlinks):
|
|
+ broken.extend(symlinks)
|
|
+
|
|
+ def _report_enabled_services_broken_symlinks_mocked(symlinks):
|
|
+ enabled_broken.extend(symlinks)
|
|
+
|
|
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[broken_symlinks, systemd_services]))
|
|
+ monkeypatch.setattr(checksystemdbrokensymlinks, '_report_broken_symlinks', _report_broken_symlinks_mocked)
|
|
+ monkeypatch.setattr(
|
|
+ checksystemdbrokensymlinks,
|
|
+ '_report_enabled_services_broken_symlinks',
|
|
+ _report_enabled_services_broken_symlinks_mocked
|
|
+ )
|
|
+
|
|
+ checksystemdbrokensymlinks.process()
|
|
+
|
|
+ assert broken == [
|
|
+ '/etc/systemd/system/multi-user.target.wants/hello.service',
|
|
+ '/etc/systemd/system/multi-user.target.wants/world.service',
|
|
+ ]
|
|
+
|
|
+ assert enabled_broken == [
|
|
+ '/etc/systemd/system/multi-user.target.wants/foo.service',
|
|
+ '/etc/systemd/system/multi-user.target.wants/bar.service',
|
|
+ ]
|
|
--
|
|
2.38.1
|
|
|