leapp-repository/SOURCES/0025-Provide-common-information-about-systemd.patch

1248 lines
49 KiB
Diff
Raw Normal View History

2023-03-28 11:22:11 +00:00
From fac07e2aeb59092871fbd9807e718c6f6da1193d Mon Sep 17 00:00:00 2001
From: Matej Matuska <mmatuska@redhat.com>
Date: Fri, 26 Aug 2022 15:33:44 +0200
Subject: [PATCH 25/32] Provide common information about systemd
Introduced new actors providing information about systemd services,
preset files, and broken symlinks. Both for the source and the
target system. All related actors are under
repos/system_upgrade/common/actors/systemd/
directory. Also it's introduced the systemd shared library
providing basic functionality for the gathering of systemd data.
Currently all functions from the library that are not called by
actors directly are marked as private. Note that provided functions
could raise exceptions. The handling of exceptions is expected to be
done inside actors. See docstrings for the set of possible exceptions.
In case of the source system, all data is valid during the upgrade
and in case the required data cannot be obtained, the upgrade
is interrupted.
However in case of data about the target system we speak about
a snapshot, how the system looks like in the moment after the upgrade
rpm transaction (data is collected during the Application phase).
The data can be used for the basic overview of the system configuration
after the upgrade transaction and also does not have to be necessary
produced! In case of an error a particular *Target* msg is not
produced, to make clear we could not collect correctly the required
data. But the upgrade is not interrupted in this phase and the errors
are logged only.
Systemd symlinks (under /etc/systemd/system/) prior the upgrade are
reported during the preupgrade, so administrator could fix them
prior the upgrade.
It's expected to create post-upgrade reports in future, but currently
skipping this topic until the post-upgrade reports are defined by
the leapp framework.
Introduced models (messages):
* SystemdBrokenSymlinksSource
* SystemdServicesInfoSource
* SystemdServicesPresetInfoSource
* SystemdBrokenSymlinksTarget
* SystemdServicesInfoTarget
* SystemdServicesPresetInfoTarget
---
.../libraries/checksystemdservicetasks.py | 10 +-
.../actors/systemd/scansystemdsource/actor.py | 25 ++
.../libraries/scansystemdsource.py | 45 +++
.../tests/test_scansystemdsource.py | 100 +++++++
.../actors/systemd/scansystemdtarget/actor.py | 28 ++
.../libraries/scansystemdtarget.py | 37 +++
.../tests/test_scansystemdtarget.py | 110 ++++++++
.../common/libraries/systemd.py | 216 ++++++++++++++
.../common/libraries/tests/00-test.preset | 10 +
.../common/libraries/tests/01-test.preset | 4 +
.../common/libraries/tests/05-invalid.preset | 8 +
.../common/libraries/tests/test_systemd.py | 263 ++++++++++++++++++
.../tests/test_systemd_files/abc.service | 0
.../tests/test_systemd_files/example.service | 0
.../tests/test_systemd_files/example.socket | 0
.../tests/test_systemd_files/extra.service | 0
.../test_systemd_files/globbed-one.service | 0
.../test_systemd_files/globbed-two.service | 0
.../test_systemd_files/template2@.service | 0
.../test_systemd_files/template@.service | 0
repos/system_upgrade/common/models/systemd.py | 155 +++++++++++
.../common/models/systemdservices.py | 22 --
22 files changed, 1005 insertions(+), 28 deletions(-)
create mode 100644 repos/system_upgrade/common/actors/systemd/scansystemdsource/actor.py
create mode 100644 repos/system_upgrade/common/actors/systemd/scansystemdsource/libraries/scansystemdsource.py
create mode 100644 repos/system_upgrade/common/actors/systemd/scansystemdsource/tests/test_scansystemdsource.py
create mode 100644 repos/system_upgrade/common/actors/systemd/scansystemdtarget/actor.py
create mode 100644 repos/system_upgrade/common/actors/systemd/scansystemdtarget/libraries/scansystemdtarget.py
create mode 100644 repos/system_upgrade/common/actors/systemd/scansystemdtarget/tests/test_scansystemdtarget.py
create mode 100644 repos/system_upgrade/common/libraries/systemd.py
create mode 100644 repos/system_upgrade/common/libraries/tests/00-test.preset
create mode 100644 repos/system_upgrade/common/libraries/tests/01-test.preset
create mode 100644 repos/system_upgrade/common/libraries/tests/05-invalid.preset
create mode 100644 repos/system_upgrade/common/libraries/tests/test_systemd.py
create mode 100644 repos/system_upgrade/common/libraries/tests/test_systemd_files/abc.service
create mode 100644 repos/system_upgrade/common/libraries/tests/test_systemd_files/example.service
create mode 100644 repos/system_upgrade/common/libraries/tests/test_systemd_files/example.socket
create mode 100644 repos/system_upgrade/common/libraries/tests/test_systemd_files/extra.service
create mode 100644 repos/system_upgrade/common/libraries/tests/test_systemd_files/globbed-one.service
create mode 100644 repos/system_upgrade/common/libraries/tests/test_systemd_files/globbed-two.service
create mode 100644 repos/system_upgrade/common/libraries/tests/test_systemd_files/template2@.service
create mode 100644 repos/system_upgrade/common/libraries/tests/test_systemd_files/template@.service
create mode 100644 repos/system_upgrade/common/models/systemd.py
delete mode 100644 repos/system_upgrade/common/models/systemdservices.py
diff --git a/repos/system_upgrade/common/actors/systemd/checksystemdservicetasks/libraries/checksystemdservicetasks.py b/repos/system_upgrade/common/actors/systemd/checksystemdservicetasks/libraries/checksystemdservicetasks.py
index 75833e4f..4d1bcda7 100644
--- a/repos/system_upgrade/common/actors/systemd/checksystemdservicetasks/libraries/checksystemdservicetasks.py
+++ b/repos/system_upgrade/common/actors/systemd/checksystemdservicetasks/libraries/checksystemdservicetasks.py
@@ -5,18 +5,16 @@ from leapp.models import SystemdServicesTasks
FMT_LIST_SEPARATOR = '\n - '
-def _printable_conflicts(conflicts):
- return FMT_LIST_SEPARATOR + FMT_LIST_SEPARATOR.join(sorted(conflicts))
-
-
def _inhibit_upgrade_with_conflicts(conflicts):
summary = (
'The requested states for systemd services on the target system are in conflict.'
- ' The following systemd services were requested to be both enabled and disabled on the target system: {}'
+ ' The following systemd services were requested to be both enabled and'
+ ' disabled on the target system:{}{}'
+ .format(FMT_LIST_SEPARATOR, FMT_LIST_SEPARATOR.join(sorted(conflicts)))
)
report = [
reporting.Title('Conflicting requirements of systemd service states'),
- reporting.Summary(summary.format(_printable_conflicts(conflicts))),
+ reporting.Summary(summary),
reporting.Severity(reporting.Severity.HIGH),
reporting.Groups([reporting.Groups.SANITY]),
reporting.Groups([reporting.Groups.INHIBITOR]),
diff --git a/repos/system_upgrade/common/actors/systemd/scansystemdsource/actor.py b/repos/system_upgrade/common/actors/systemd/scansystemdsource/actor.py
new file mode 100644
index 00000000..04a504b9
--- /dev/null
+++ b/repos/system_upgrade/common/actors/systemd/scansystemdsource/actor.py
@@ -0,0 +1,25 @@
+from leapp.actors import Actor
+from leapp.libraries.actor import scansystemdsource
+from leapp.models import SystemdBrokenSymlinksSource, SystemdServicesInfoSource, SystemdServicesPresetInfoSource
+from leapp.tags import FactsPhaseTag, IPUWorkflowTag
+
+
+class ScanSystemdSource(Actor):
+ """
+ Provides info about systemd on the source system
+
+ The provided info includes information about:
+ - vendor presets of services
+ - systemd service files, including their state
+ - broken systemd symlinks
+
+ There is an analogous actor :class:`ScanSystemdTarget` for target system.
+ """
+
+ name = 'scan_systemd_source'
+ consumes = ()
+ produces = (SystemdBrokenSymlinksSource, SystemdServicesInfoSource, SystemdServicesPresetInfoSource)
+ tags = (IPUWorkflowTag, FactsPhaseTag)
+
+ def process(self):
+ scansystemdsource.scan()
diff --git a/repos/system_upgrade/common/actors/systemd/scansystemdsource/libraries/scansystemdsource.py b/repos/system_upgrade/common/actors/systemd/scansystemdsource/libraries/scansystemdsource.py
new file mode 100644
index 00000000..f6d9599c
--- /dev/null
+++ b/repos/system_upgrade/common/actors/systemd/scansystemdsource/libraries/scansystemdsource.py
@@ -0,0 +1,45 @@
+from leapp.exceptions import StopActorExecutionError
+from leapp.libraries.common import systemd
+from leapp.libraries.stdlib import api, CalledProcessError
+from leapp.models import SystemdBrokenSymlinksSource, SystemdServicesInfoSource, SystemdServicesPresetInfoSource
+
+
+def scan():
+ try:
+ broken_symlinks = systemd.get_broken_symlinks()
+ except (OSError, CalledProcessError) as err:
+ details = {'details': str(err)}
+ if isinstance(err, CalledProcessError):
+ details['stderr'] = err.stderr
+ raise StopActorExecutionError(
+ message='Cannot scan the system to list possible broken systemd symlinks.',
+ details=details
+ )
+
+ try:
+ services_files = systemd.get_service_files()
+ except CalledProcessError as err:
+ raise StopActorExecutionError(
+ message='Cannot obtain the list of systemd service unit files.',
+ details={'details': str(err), 'stderr': err.stderr}
+ )
+
+ try:
+ presets = systemd.get_system_service_preset_files(services_files, ignore_invalid_entries=False)
+ except (OSError, CalledProcessError) as err:
+ details = {'details': str(err)}
+ if isinstance(err, CalledProcessError):
+ details['stderr'] = err.stderr
+ raise StopActorExecutionError(
+ message='Cannot obtain the list of systemd preset files.',
+ details=details
+ )
+ except ValueError as err:
+ raise StopActorExecutionError(
+ message='Discovered an invalid systemd preset file.',
+ details={'details': str(err)}
+ )
+
+ api.produce(SystemdBrokenSymlinksSource(broken_symlinks=broken_symlinks))
+ api.produce(SystemdServicesInfoSource(service_files=services_files))
+ api.produce(SystemdServicesPresetInfoSource(presets=presets))
diff --git a/repos/system_upgrade/common/actors/systemd/scansystemdsource/tests/test_scansystemdsource.py b/repos/system_upgrade/common/actors/systemd/scansystemdsource/tests/test_scansystemdsource.py
new file mode 100644
index 00000000..7b95a2df
--- /dev/null
+++ b/repos/system_upgrade/common/actors/systemd/scansystemdsource/tests/test_scansystemdsource.py
@@ -0,0 +1,100 @@
+import pytest
+
+from leapp.exceptions import StopActorExecutionError
+from leapp.libraries.actor import scansystemdsource
+from leapp.libraries.common import systemd
+from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, produce_mocked
+from leapp.libraries.stdlib import api, CalledProcessError
+from leapp.models import (
+ SystemdServiceFile,
+ SystemdServicePreset,
+ SystemdServicesInfoSource,
+ SystemdServicesPresetInfoSource
+)
+
+_BROKEN_SYMLINKS = [
+ "/etc/systemd/system/multi-user.target.wants/vdo.service",
+ "/etc/systemd/system/multi-user.target.wants/rngd.service"
+]
+
+_SERVICE_FILES = [
+ SystemdServiceFile(name='getty@.service', state='enabled'),
+ SystemdServiceFile(name='vdo.service', state='disabled')
+]
+
+_PRESETS = [
+ SystemdServicePreset(service='getty@.service', state='enable'),
+ SystemdServicePreset(service='vdo.service', state='disable'),
+]
+
+
+@pytest.mark.parametrize(
+ ('broken_symlinks', 'files', 'presets'),
+ (
+ (_BROKEN_SYMLINKS, _SERVICE_FILES, _PRESETS),
+ ([], [], [])
+ )
+)
+def test_message_produced(monkeypatch, broken_symlinks, files, presets):
+
+ def get_broken_symlinks_mocked():
+ return broken_symlinks
+
+ def get_service_files_mocked():
+ return files
+
+ def get_system_service_preset_files_mocked(service_files, ignore_invalid_entries):
+ return presets
+
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked())
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+ monkeypatch.setattr(systemd, 'get_broken_symlinks', get_broken_symlinks_mocked)
+ monkeypatch.setattr(systemd, 'get_service_files', get_service_files_mocked)
+ monkeypatch.setattr(systemd, 'get_system_service_preset_files', get_system_service_preset_files_mocked)
+
+ scansystemdsource.scan()
+
+ assert api.produce.called
+ assert api.produce.model_instances[0].broken_symlinks == broken_symlinks
+ assert api.produce.model_instances[1].service_files == files
+ assert api.produce.model_instances[2].presets == presets
+
+
+_CALL_PROC_ERR = CalledProcessError(
+ message='BooCalled',
+ command=['find'],
+ result={
+ 'stdout': 'stdout',
+ 'stderr': 'stderr',
+ 'exit_code': 1,
+ 'signal': 1,
+ 'pid': 1,
+ }
+)
+
+
+class GetOrRaise(object):
+ def __init__(self, value):
+ self.value = value
+
+ def __call__(self, *dummyArgs, **dummy):
+ if isinstance(self.value, list):
+ return self.value
+ raise self.value
+
+
+@pytest.mark.parametrize('symlinks', [OSError('Boo'), _CALL_PROC_ERR, []])
+@pytest.mark.parametrize('files', [_CALL_PROC_ERR, []])
+@pytest.mark.parametrize('presets', [OSError('Boo'), _CALL_PROC_ERR, ValueError('Hamster'), []])
+def test_exception_handling(monkeypatch, symlinks, files, presets):
+ if symlinks == files == presets == []:
+ # covered by test above
+ return
+
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked())
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+ monkeypatch.setattr(systemd, 'get_broken_symlinks', GetOrRaise(symlinks))
+ monkeypatch.setattr(systemd, 'get_service_files', GetOrRaise(files))
+ monkeypatch.setattr(systemd, 'get_system_service_preset_files', GetOrRaise(presets))
+ with pytest.raises(StopActorExecutionError):
+ scansystemdsource.scan()
diff --git a/repos/system_upgrade/common/actors/systemd/scansystemdtarget/actor.py b/repos/system_upgrade/common/actors/systemd/scansystemdtarget/actor.py
new file mode 100644
index 00000000..185b30ac
--- /dev/null
+++ b/repos/system_upgrade/common/actors/systemd/scansystemdtarget/actor.py
@@ -0,0 +1,28 @@
+from leapp.actors import Actor
+from leapp.libraries.actor import scansystemdtarget
+from leapp.models import SystemdBrokenSymlinksTarget, SystemdServicesInfoTarget, SystemdServicesPresetInfoTarget
+from leapp.tags import ApplicationsPhaseTag, IPUWorkflowTag
+
+
+class ScanSystemdTarget(Actor):
+ """
+ Provides info about systemd on the source system
+
+ The provided info includes information about:
+ - vendor presets of services
+ - systemd service files, including their state
+ - broken systemd symlinks
+
+ There is an analogous actor :class:`ScanSystemdSource` for source system
+
+ The actor ignore errors (errors are logged, but do not stop the upgrade).
+ If some data cannot be obtained, particular message is not produced.
+ Actors are expected to check whether the data is available.
+ """
+ name = 'scan_systemd_target'
+ consumes = ()
+ produces = (SystemdBrokenSymlinksTarget, SystemdServicesInfoTarget, SystemdServicesPresetInfoTarget)
+ tags = (IPUWorkflowTag, ApplicationsPhaseTag)
+
+ def process(self):
+ scansystemdtarget.scan()
diff --git a/repos/system_upgrade/common/actors/systemd/scansystemdtarget/libraries/scansystemdtarget.py b/repos/system_upgrade/common/actors/systemd/scansystemdtarget/libraries/scansystemdtarget.py
new file mode 100644
index 00000000..9c922c93
--- /dev/null
+++ b/repos/system_upgrade/common/actors/systemd/scansystemdtarget/libraries/scansystemdtarget.py
@@ -0,0 +1,37 @@
+from leapp.libraries.common import systemd
+from leapp.libraries.stdlib import api, CalledProcessError
+from leapp.models import SystemdBrokenSymlinksTarget, SystemdServicesInfoTarget, SystemdServicesPresetInfoTarget
+
+
+def scan_broken_symlinks():
+ try:
+ broken_symlinks = systemd.get_broken_symlinks()
+ except (OSError, CalledProcessError):
+ return
+ api.produce(SystemdBrokenSymlinksTarget(broken_symlinks=broken_symlinks))
+
+
+def scan_service_files():
+ try:
+ services_files = systemd.get_service_files()
+ except CalledProcessError:
+ return None
+ api.produce(SystemdServicesInfoTarget(service_files=services_files))
+ return services_files
+
+
+def scan_preset_files(services_files):
+ if services_files is None:
+ return
+ try:
+ presets = systemd.get_system_service_preset_files(services_files, ignore_invalid_entries=True)
+ except (OSError, CalledProcessError):
+ return
+ api.produce(SystemdServicesPresetInfoTarget(presets=presets))
+
+
+def scan():
+ # Errors are logged inside the systemd library, no need to log them here again.
+ scan_broken_symlinks()
+ services_files = scan_service_files()
+ scan_preset_files(services_files)
diff --git a/repos/system_upgrade/common/actors/systemd/scansystemdtarget/tests/test_scansystemdtarget.py b/repos/system_upgrade/common/actors/systemd/scansystemdtarget/tests/test_scansystemdtarget.py
new file mode 100644
index 00000000..227ba61a
--- /dev/null
+++ b/repos/system_upgrade/common/actors/systemd/scansystemdtarget/tests/test_scansystemdtarget.py
@@ -0,0 +1,110 @@
+import pytest
+
+from leapp.libraries.actor import scansystemdtarget
+from leapp.libraries.common import systemd
+from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, produce_mocked
+from leapp.libraries.stdlib import api, CalledProcessError
+from leapp.models import (
+ SystemdBrokenSymlinksTarget,
+ SystemdServiceFile,
+ SystemdServicePreset,
+ SystemdServicesInfoTarget,
+ SystemdServicesPresetInfoTarget
+)
+
+_BROKEN_SYMLINKS = [
+ "/etc/systemd/system/multi-user.target.wants/vdo.service",
+ "/etc/systemd/system/multi-user.target.wants/rngd.service"
+]
+
+_SERVICE_FILES = [
+ SystemdServiceFile(name='getty@.service', state='enabled'),
+ SystemdServiceFile(name='vdo.service', state='disabled')
+]
+
+_PRESETS = [
+ SystemdServicePreset(service='getty@.service', state='enable'),
+ SystemdServicePreset(service='vdo.service', state='disable'),
+]
+
+
+@pytest.mark.parametrize(
+ ('broken_symlinks', 'files', 'presets'),
+ (
+ (_BROKEN_SYMLINKS, _SERVICE_FILES, _PRESETS),
+ ([], [], [])
+ )
+)
+def test_message_produced(monkeypatch, broken_symlinks, files, presets):
+
+ def scan_broken_symlinks_mocked():
+ return broken_symlinks
+
+ def get_service_files_mocked():
+ return files
+
+ def get_system_service_preset_files_mocked(service_files, ignore_invalid_entries):
+ return presets
+
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked())
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+ monkeypatch.setattr(systemd, 'get_broken_symlinks', scan_broken_symlinks_mocked)
+ monkeypatch.setattr(systemd, 'get_service_files', get_service_files_mocked)
+ monkeypatch.setattr(systemd, 'get_system_service_preset_files', get_system_service_preset_files_mocked)
+
+ scansystemdtarget.scan()
+
+ assert api.produce.called
+ assert api.produce.model_instances[0].broken_symlinks == broken_symlinks
+ assert api.produce.model_instances[1].service_files == files
+ assert api.produce.model_instances[2].presets == presets
+
+
+_CALL_PROC_ERR = CalledProcessError(
+ message='BooCalled',
+ command=['find'],
+ result={
+ 'stdout': 'stdout',
+ 'stderr': 'stderr',
+ 'exit_code': 1,
+ 'signal': 1,
+ 'pid': 1,
+ }
+)
+
+
+class GetOrRaise(object):
+ def __init__(self, value):
+ self.value = value
+
+ def __call__(self, *dummyArgs, **dummy):
+ if isinstance(self.value, list):
+ return self.value
+ raise self.value
+
+
+@pytest.mark.parametrize('symlinks', [OSError('Boo'), _CALL_PROC_ERR, []])
+@pytest.mark.parametrize('files', [_CALL_PROC_ERR, []])
+@pytest.mark.parametrize('presets', [OSError('Boo'), _CALL_PROC_ERR, []])
+def test_exception_handling(monkeypatch, symlinks, files, presets):
+
+ def check_msg(input_data, msg_type, msgs, is_msg_expected):
+ for msg in msgs.model_instances:
+ if isinstance(msg, msg_type):
+ return is_msg_expected
+ return not is_msg_expected
+
+ if symlinks == files == presets == []:
+ # covered by test above
+ return
+
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked())
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+ monkeypatch.setattr(systemd, 'get_broken_symlinks', GetOrRaise(symlinks))
+ monkeypatch.setattr(systemd, 'get_service_files', GetOrRaise(files))
+ monkeypatch.setattr(systemd, 'get_system_service_preset_files', GetOrRaise(presets))
+ scansystemdtarget.scan()
+ assert check_msg(symlinks, SystemdBrokenSymlinksTarget, api.produce, isinstance(symlinks, list))
+ assert check_msg(files, SystemdServicesInfoTarget, api.produce, isinstance(files, list))
+ is_msg_expected = isinstance(files, list) and isinstance(presets, list)
+ assert check_msg(presets, SystemdServicesPresetInfoTarget, api.produce, is_msg_expected)
diff --git a/repos/system_upgrade/common/libraries/systemd.py b/repos/system_upgrade/common/libraries/systemd.py
new file mode 100644
index 00000000..bbf71af7
--- /dev/null
+++ b/repos/system_upgrade/common/libraries/systemd.py
@@ -0,0 +1,216 @@
+import fnmatch
+import os
+
+from leapp.libraries.stdlib import api, CalledProcessError, run
+from leapp.models import SystemdServiceFile, SystemdServicePreset
+
+SYSTEMD_SYMLINKS_DIR = '/etc/systemd/system/'
+
+_SYSTEMCTL_CMD_OPTIONS = ['--type=service', '--all', '--plain', '--no-legend']
+_USR_PRESETS_PATH = '/usr/lib/systemd/system-preset/'
+_ETC_PRESETS_PATH = '/etc/systemd/system-preset/'
+
+SYSTEMD_SYSTEM_LOAD_PATH = [
+ '/etc/systemd/system',
+ '/usr/lib/systemd/system'
+]
+
+
+def get_broken_symlinks():
+ """
+ Get broken systemd symlinks on the system
+
+ :return: List of broken systemd symlinks
+ :rtype: list[str]
+ :raises: CalledProcessError: if the `find` command fails
+ :raises: OSError: if the find utility is not found
+ """
+ try:
+ return run(['find', SYSTEMD_SYMLINKS_DIR, '-xtype', 'l'], split=True)['stdout']
+ except (OSError, CalledProcessError):
+ api.current_logger().error('Cannot obtain the list of broken systemd symlinks.')
+ raise
+
+
+def get_service_files():
+ """
+ Get list of unit files of systemd services on the system
+
+ The list includes template units.
+
+ :return: List of service unit files with states
+ :rtype: list[SystemdServiceFile]
+ :raises: CalledProcessError: in case of failure of `systemctl` command
+ """
+ services_files = []
+ try:
+ cmd = ['systemctl', 'list-unit-files'] + _SYSTEMCTL_CMD_OPTIONS
+ service_units_data = run(cmd, split=True)['stdout']
+ except CalledProcessError as err:
+ api.current_logger().error('Cannot obtain the list of unit files:{}'.format(str(err)))
+ raise
+
+ for entry in service_units_data:
+ columns = entry.split()
+ services_files.append(SystemdServiceFile(name=columns[0], state=columns[1]))
+ return services_files
+
+
+def _join_presets_resolving_overrides(etc_files, usr_files):
+ """
+ Join presets and resolve preset file overrides
+
+ Preset files in /etc/ override those with the same name in /usr/.
+ If such a file is a symlink to /dev/null, it disables the one in /usr/ instead.
+
+ :param etc_files: Systemd preset files in /etc/
+ :param usr_files: Systemd preset files in /usr/
+ :return: List of preset files in /etc/ and /usr/ with overridden files removed
+ """
+ for etc_file in etc_files:
+ filename = os.path.basename(etc_file)
+ for usr_file in usr_files:
+ if filename == os.path.basename(usr_file):
+ usr_files.remove(usr_file)
+ if os.path.islink(etc_file) and os.readlink(etc_file) == '/dev/null':
+ etc_files.remove(etc_file)
+
+ return etc_files + usr_files
+
+
+def _search_preset_files(path):
+ """
+ Search preset files in the given path
+
+ Presets are search recursively in the given directory.
+ If path isn't an existing directory, return empty list.
+
+ :param path: The path to search preset files in
+ :return: List of found preset files
+ :rtype: list[str]
+ :raises: CalledProcessError: if the `find` command fails
+ :raises: OSError: if the find utility is not found
+ """
+ if os.path.isdir(path):
+ try:
+ return run(['find', path, '-name', '*.preset'], split=True)['stdout']
+ except (OSError, CalledProcessError) as err:
+ api.current_logger().error('Cannot obtain list of systemd preset files in {}:{}'.format(path, str(err)))
+ raise
+ else:
+ return []
+
+
+def _get_system_preset_files():
+ """
+ Get systemd system preset files and remove overriding entries. Entries in /run/systemd/system are ignored.
+
+ :return: List of system systemd preset files
+ :raises: CalledProcessError: if the `find` command fails
+ :raises: OSError: if the find utility is not found
+ """
+ etc_files = _search_preset_files(_ETC_PRESETS_PATH)
+ usr_files = _search_preset_files(_USR_PRESETS_PATH)
+
+ preset_files = _join_presets_resolving_overrides(etc_files, usr_files)
+ preset_files.sort()
+ return preset_files
+
+
+def _recursive_glob(pattern, root_dir):
+ for _, _, filenames in os.walk(root_dir):
+ for filename in filenames:
+ if fnmatch.fnmatch(filename, pattern):
+ yield filename
+
+
+def _parse_preset_entry(entry, presets, load_path):
+ """
+ Parse a single entry (line) in a preset file
+
+ Single entry might set presets on multiple units using globs.
+
+ :param entry: The entry to parse
+ :param presets: Dictionary to store the presets into
+ :param load_path: List of paths to look systemd unit files up in
+ """
+
+ columns = entry.split()
+ if len(columns) < 2 or columns[0] not in ('enable', 'disable'):
+ raise ValueError('Invalid preset file entry: "{}"'.format(entry))
+
+ for path in load_path:
+ # TODO(mmatuska): This currently also globs non unit files,
+ # so the results need to be filtered with something like endswith('.<unit_type>')
+ unit_files = _recursive_glob(columns[1], root_dir=path)
+
+ for unit_file in unit_files:
+ if '@' in columns[1] and len(columns) > 2:
+ # unit is a template,
+ # if the entry contains instance names after template unit name
+ # the entry only applies to the specified instances, not to the
+ # template itself
+ for instance in columns[2:]:
+ service_name = unit_file[:unit_file.index('@') + 1] + instance + '.service'
+ if service_name not in presets: # first occurrence has priority
+ presets[service_name] = columns[0]
+
+ elif unit_file not in presets: # first occurrence has priority
+ presets[unit_file] = columns[0]
+
+
+def _parse_preset_files(preset_files, load_path, ignore_invalid_entries):
+ """
+ Parse presets from preset files
+
+ :param load_path: List of paths to search units at
+ :param ignore_invalid_entries: Whether to ignore invalid entries in preset files or raise an error
+ :return: Dictionary mapping systemd units to their preset state
+ :rtype: dict[str, str]
+ :raises: ValueError: when a preset file has invalid content
+ """
+ presets = {}
+
+ for preset in preset_files:
+ with open(preset, 'r') as preset_file:
+ for line in preset_file:
+ stripped = line.strip()
+ if stripped and stripped[0] not in ('#', ';'): # ignore comments
+ try:
+ _parse_preset_entry(stripped, presets, load_path)
+ except ValueError as err:
+ new_msg = 'Invalid preset file {pfile}: {error}'.format(pfile=preset, error=str(err))
+ if ignore_invalid_entries:
+ api.current_logger().warning(new_msg)
+ continue
+ raise ValueError(new_msg)
+ return presets
+
+
+def get_system_service_preset_files(service_files, ignore_invalid_entries=False):
+ """
+ Get system preset files for services
+
+ Presets for static and transient services are filtered out.
+
+ :param services_files: List of service unit files
+ :param ignore_invalid_entries: Ignore invalid entries in preset files if True, raise ValueError otherwise
+ :return: List of system systemd services presets
+ :rtype: list[SystemdServicePreset]
+ :raises: CalledProcessError: In case of errors when discovering systemd preset files
+ :raises: OSError: When the `find` command is not available
+ :raises: ValueError: When a preset file has invalid content and ignore_invalid_entries is False
+ """
+ preset_files = _get_system_preset_files()
+ presets = _parse_preset_files(preset_files, SYSTEMD_SYSTEM_LOAD_PATH, ignore_invalid_entries)
+
+ preset_models = []
+ for unit, state in presets.items():
+ if unit.endswith('.service'):
+ service_file = next(iter([s for s in service_files if s.name == unit]), None)
+ # presets can also be set on instances of template services which don't have a unit file
+ if service_file and service_file.state in ('static', 'transient'):
+ continue
+ preset_models.append(SystemdServicePreset(service=unit, state=state))
+
+ return preset_models
diff --git a/repos/system_upgrade/common/libraries/tests/00-test.preset b/repos/system_upgrade/common/libraries/tests/00-test.preset
new file mode 100644
index 00000000..85e4cb0b
--- /dev/null
+++ b/repos/system_upgrade/common/libraries/tests/00-test.preset
@@ -0,0 +1,10 @@
+enable example.service
+# first line takes priority
+disable example.service
+
+# hello, world!
+disable abc.service
+
+; another comment format
+disable template@.service
+enable template@.service instance1 instance2
diff --git a/repos/system_upgrade/common/libraries/tests/01-test.preset b/repos/system_upgrade/common/libraries/tests/01-test.preset
new file mode 100644
index 00000000..6ef393c4
--- /dev/null
+++ b/repos/system_upgrade/common/libraries/tests/01-test.preset
@@ -0,0 +1,4 @@
+disable example.*
+enable globbed*.service
+
+disable *
diff --git a/repos/system_upgrade/common/libraries/tests/05-invalid.preset b/repos/system_upgrade/common/libraries/tests/05-invalid.preset
new file mode 100644
index 00000000..9ec39de1
--- /dev/null
+++ b/repos/system_upgrade/common/libraries/tests/05-invalid.preset
@@ -0,0 +1,8 @@
+# missing unit or glob
+enable
+; missing enable or disable
+hello.service
+# only enable and disable directives are allowed
+mask hello.service
+
+disable example.service
diff --git a/repos/system_upgrade/common/libraries/tests/test_systemd.py b/repos/system_upgrade/common/libraries/tests/test_systemd.py
new file mode 100644
index 00000000..a91fce11
--- /dev/null
+++ b/repos/system_upgrade/common/libraries/tests/test_systemd.py
@@ -0,0 +1,263 @@
+import os
+from functools import partial
+
+import pytest
+
+from leapp.libraries.common import systemd
+from leapp.libraries.common.testutils import logger_mocked
+from leapp.libraries.stdlib import api
+from leapp.models import SystemdServiceFile, SystemdServicePreset
+
+CURR_DIR = os.path.dirname(os.path.abspath(__file__))
+
+
+def test_get_service_files(monkeypatch):
+ def run_mocked(cmd, *args, **kwargs):
+ if cmd == ['systemctl', 'list-unit-files'] + systemd._SYSTEMCTL_CMD_OPTIONS:
+ return {'stdout': [
+ 'auditd.service enabled',
+ 'crond.service enabled ',
+ 'dbus.service static ',
+ 'dnf-makecache.service static ',
+ 'firewalld.service enabled ',
+ 'getty@.service enabled ',
+ 'gssproxy.service disabled',
+ 'kdump.service enabled ',
+ 'mdmon@.service static ',
+ 'nfs.service disabled',
+ 'polkit.service static ',
+ 'rescue.service static ',
+ 'rngd.service enabled ',
+ 'rsyncd.service disabled',
+ 'rsyncd@.service static ',
+ 'smartd.service enabled ',
+ 'sshd.service enabled ',
+ 'sshd@.service static ',
+ 'wpa_supplicant.service disabled'
+ ]}
+ raise ValueError('Attempted to call unexpected command: {}'.format(cmd))
+
+ monkeypatch.setattr(systemd, 'run', run_mocked)
+ service_files = systemd.get_service_files()
+
+ expected = [
+ SystemdServiceFile(name='auditd.service', state='enabled'),
+ SystemdServiceFile(name='crond.service', state='enabled'),
+ SystemdServiceFile(name='dbus.service', state='static'),
+ SystemdServiceFile(name='dnf-makecache.service', state='static'),
+ SystemdServiceFile(name='firewalld.service', state='enabled'),
+ SystemdServiceFile(name='getty@.service', state='enabled'),
+ SystemdServiceFile(name='gssproxy.service', state='disabled'),
+ SystemdServiceFile(name='kdump.service', state='enabled'),
+ SystemdServiceFile(name='mdmon@.service', state='static'),
+ SystemdServiceFile(name='nfs.service', state='disabled'),
+ SystemdServiceFile(name='polkit.service', state='static'),
+ SystemdServiceFile(name='rescue.service', state='static'),
+ SystemdServiceFile(name='rngd.service', state='enabled'),
+ SystemdServiceFile(name='rsyncd.service', state='disabled'),
+ SystemdServiceFile(name='rsyncd@.service', state='static'),
+ SystemdServiceFile(name='smartd.service', state='enabled'),
+ SystemdServiceFile(name='sshd.service', state='enabled'),
+ SystemdServiceFile(name='sshd@.service', state='static'),
+ SystemdServiceFile(name='wpa_supplicant.service', state='disabled')
+ ]
+
+ assert service_files == expected
+
+
+def test_preset_files_overrides():
+ etc_files = [
+ '/etc/systemd/system-preset/00-abc.preset',
+ '/etc/systemd/system-preset/preset_without_prio.preset'
+ ]
+ usr_files = [
+ '/usr/lib/systemd/system-preset/00-abc.preset',
+ '/usr/lib/systemd/system-preset/99-xyz.preset',
+ '/usr/lib/systemd/system-preset/preset_without_prio.preset'
+ ]
+
+ expected = [
+ '/usr/lib/systemd/system-preset/99-xyz.preset',
+ '/etc/systemd/system-preset/00-abc.preset',
+ '/etc/systemd/system-preset/preset_without_prio.preset'
+ ]
+
+ presets = systemd._join_presets_resolving_overrides(etc_files, usr_files)
+ assert sorted(presets) == sorted(expected)
+
+
+def test_preset_files_block_override(monkeypatch):
+ etc_files = [
+ '/etc/systemd/system-preset/00-abc.preset'
+ ]
+ usr_files = [
+ '/usr/lib/systemd/system-preset/00-abc.preset',
+ '/usr/lib/systemd/system-preset/99-xyz.preset'
+ ]
+
+ expected = [
+ '/usr/lib/systemd/system-preset/99-xyz.preset',
+ ]
+
+ def islink_mocked(path):
+ return path == '/etc/systemd/system-preset/00-abc.preset'
+
+ def readlink_mocked(path):
+ if path == '/etc/systemd/system-preset/00-abc.preset':
+ return '/dev/null'
+ raise OSError
+
+ monkeypatch.setattr(os.path, 'islink', islink_mocked)
+ monkeypatch.setattr(os, 'readlink', readlink_mocked)
+
+ presets = systemd._join_presets_resolving_overrides(etc_files, usr_files)
+ assert sorted(presets) == sorted(expected)
+
+
+TEST_SYSTEMD_LOAD_PATH = [os.path.join(CURR_DIR, 'test_systemd_files/')]
+
+TESTING_PRESET_FILES = [
+ os.path.join(CURR_DIR, '00-test.preset'),
+ os.path.join(CURR_DIR, '01-test.preset')
+]
+
+TESTING_PRESET_WITH_INVALID_ENTRIES = os.path.join(CURR_DIR, '05-invalid.preset')
+
+_PARSE_PRESET_ENTRIES_TEST_DEFINITION = (
+ ('enable example.service', {'example.service': 'enable'}),
+ ('disable abc.service', {'abc.service': 'disable'}),
+ ('enable template@.service', {'template@.service': 'enable'}),
+ ('disable template2@.service', {'template2@.service': 'disable'}),
+ ('disable template@.service instance1 instance2', {
+ 'template@instance1.service': 'disable',
+ 'template@instance2.service': 'disable'
+ }),
+ ('enable globbed*.service', {'globbed-one.service': 'enable', 'globbed-two.service': 'enable'}),
+ ('enable example.*', {'example.service': 'enable', 'example.socket': 'enable'}),
+ ('disable *', {
+ 'example.service': 'disable',
+ 'abc.service': 'disable',
+ 'template@.service': 'disable',
+ 'template2@.service': 'disable',
+ 'globbed-one.service': 'disable',
+ 'globbed-two.service': 'disable',
+ 'example.socket': 'disable',
+ 'extra.service': 'disable'
+ })
+)
+
+
+@pytest.mark.parametrize('entry,expected', _PARSE_PRESET_ENTRIES_TEST_DEFINITION)
+def test_parse_preset_entry(monkeypatch, entry, expected):
+ presets = {}
+ systemd._parse_preset_entry(entry, presets, TEST_SYSTEMD_LOAD_PATH)
+ assert presets == expected
+
+
+@pytest.mark.parametrize(
+ 'entry',
+ [
+ ('hello.service'),
+ ('mask hello.service'),
+ ('enable'),
+ ]
+)
+def test_parse_preset_entry_invalid(monkeypatch, entry):
+ presets = {}
+ with pytest.raises(ValueError, match=r'^Invalid preset file entry: '):
+ systemd._parse_preset_entry(entry, presets, TEST_SYSTEMD_LOAD_PATH)
+
+
+def test_parse_preset_files(monkeypatch):
+
+ expected = {
+ 'example.service': 'enable',
+ 'example.socket': 'disable',
+ 'abc.service': 'disable',
+ 'template@.service': 'disable',
+ 'template@instance1.service': 'enable',
+ 'template@instance2.service': 'enable',
+ 'globbed-one.service': 'enable',
+ 'globbed-two.service': 'enable',
+ 'extra.service': 'disable',
+ 'template2@.service': 'disable'
+ }
+
+ presets = systemd._parse_preset_files(TESTING_PRESET_FILES, TEST_SYSTEMD_LOAD_PATH, False)
+ assert presets == expected
+
+
+def test_parse_preset_files_invalid():
+ with pytest.raises(ValueError):
+ systemd._parse_preset_files(
+ [TESTING_PRESET_WITH_INVALID_ENTRIES], TEST_SYSTEMD_LOAD_PATH, ignore_invalid_entries=False
+ )
+
+
+def test_parse_preset_files_ignore_invalid(monkeypatch):
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
+
+ invalid_preset_files = [TESTING_PRESET_WITH_INVALID_ENTRIES]
+ presets = systemd._parse_preset_files(
+ invalid_preset_files, TEST_SYSTEMD_LOAD_PATH, ignore_invalid_entries=True
+ )
+
+ for entry in ('enable', 'hello.service', 'mask hello.service'):
+ msg = 'Invalid preset file {}: Invalid preset file entry: "{}"'.format(invalid_preset_files[0], entry)
+ assert msg in api.current_logger.warnmsg
+
+ assert presets == {'example.service': 'disable'}
+
+
+def parse_preset_files_mocked():
+ mocked = partial(systemd._parse_preset_files, load_path=TEST_SYSTEMD_LOAD_PATH)
+
+ def impl(preset_files, load_path, ignore_invalid_entries):
+ return mocked(preset_files, ignore_invalid_entries=ignore_invalid_entries)
+ return impl
+
+
+def test_get_service_preset_files(monkeypatch):
+
+ def get_system_preset_files_mocked():
+ return TESTING_PRESET_FILES
+
+ monkeypatch.setattr(systemd, '_get_system_preset_files', get_system_preset_files_mocked)
+ monkeypatch.setattr(systemd, '_parse_preset_files', parse_preset_files_mocked())
+
+ service_files = [
+ SystemdServiceFile(name='abc.service', state='transient'),
+ SystemdServiceFile(name='example.service', state='static'),
+ SystemdServiceFile(name='example.socket', state='masked'),
+ SystemdServiceFile(name='extra.service', state='disabled'),
+ SystemdServiceFile(name='template2@.service', state='enabled'),
+ SystemdServiceFile(name='template@.service', state='enabled'),
+ ]
+
+ expected = [
+ # dont expect example.service since it's static
+ # dont expect abc.service since it's transient
+ SystemdServicePreset(service='template@.service', state='disable'),
+ SystemdServicePreset(service='template@instance1.service', state='enable'),
+ SystemdServicePreset(service='template@instance2.service', state='enable'),
+ SystemdServicePreset(service='globbed-one.service', state='enable'),
+ SystemdServicePreset(service='globbed-two.service', state='enable'),
+ SystemdServicePreset(service='extra.service', state='disable'),
+ SystemdServicePreset(service='template2@.service', state='disable')
+ ]
+
+ presets = systemd.get_system_service_preset_files(service_files, False)
+ assert sorted(presets, key=lambda e: e.service) == sorted(expected, key=lambda e: e.service)
+
+
+def test_get_service_preset_files_invalid(monkeypatch):
+
+ def get_system_preset_files_mocked():
+ return [TESTING_PRESET_WITH_INVALID_ENTRIES]
+
+ monkeypatch.setattr(systemd, '_get_system_preset_files', get_system_preset_files_mocked)
+ monkeypatch.setattr(systemd, '_parse_preset_files', parse_preset_files_mocked())
+
+ with pytest.raises(ValueError):
+ # doesn't matter what service_files are
+ systemd.get_system_service_preset_files([], ignore_invalid_entries=False)
diff --git a/repos/system_upgrade/common/libraries/tests/test_systemd_files/abc.service b/repos/system_upgrade/common/libraries/tests/test_systemd_files/abc.service
new file mode 100644
index 00000000..e69de29b
diff --git a/repos/system_upgrade/common/libraries/tests/test_systemd_files/example.service b/repos/system_upgrade/common/libraries/tests/test_systemd_files/example.service
new file mode 100644
index 00000000..e69de29b
diff --git a/repos/system_upgrade/common/libraries/tests/test_systemd_files/example.socket b/repos/system_upgrade/common/libraries/tests/test_systemd_files/example.socket
new file mode 100644
index 00000000..e69de29b
diff --git a/repos/system_upgrade/common/libraries/tests/test_systemd_files/extra.service b/repos/system_upgrade/common/libraries/tests/test_systemd_files/extra.service
new file mode 100644
index 00000000..e69de29b
diff --git a/repos/system_upgrade/common/libraries/tests/test_systemd_files/globbed-one.service b/repos/system_upgrade/common/libraries/tests/test_systemd_files/globbed-one.service
new file mode 100644
index 00000000..e69de29b
diff --git a/repos/system_upgrade/common/libraries/tests/test_systemd_files/globbed-two.service b/repos/system_upgrade/common/libraries/tests/test_systemd_files/globbed-two.service
new file mode 100644
index 00000000..e69de29b
diff --git a/repos/system_upgrade/common/libraries/tests/test_systemd_files/template2@.service b/repos/system_upgrade/common/libraries/tests/test_systemd_files/template2@.service
new file mode 100644
index 00000000..e69de29b
diff --git a/repos/system_upgrade/common/libraries/tests/test_systemd_files/template@.service b/repos/system_upgrade/common/libraries/tests/test_systemd_files/template@.service
new file mode 100644
index 00000000..e69de29b
diff --git a/repos/system_upgrade/common/models/systemd.py b/repos/system_upgrade/common/models/systemd.py
new file mode 100644
index 00000000..f66ae5dd
--- /dev/null
+++ b/repos/system_upgrade/common/models/systemd.py
@@ -0,0 +1,155 @@
+from leapp.models import fields, Model
+from leapp.topics import SystemInfoTopic
+
+
+class SystemdBrokenSymlinksSource(Model):
+ """
+ Information about broken systemd symlinks on the source system
+ """
+
+ topic = SystemInfoTopic
+ broken_symlinks = fields.List(fields.String(), default=[])
+ """
+ List of broken systemd symlinks on the source system
+
+ The values are absolute paths of the broken symlinks.
+ """
+
+
+class SystemdBrokenSymlinksTarget(SystemdBrokenSymlinksSource):
+ """
+ Analogy to :class:`SystemdBrokenSymlinksSource`, but for the target system
+ """
+
+
+class SystemdServicesTasks(Model):
+ """
+ Influence the systemd services of the target system
+
+ E.g. it could be specified explicitly whether some services should
+ be enabled or disabled after the in-place upgrade - follow descriptions
+ of particular tasks for details.
+
+ In case of conflicting tasks (e.g. the A service should be enabled and
+ disabled in the same time):
+ a) If conflicting tasks are detected during check phases,
+ the upgrade is inhibited with the proper report.
+ b) If conflicting tasks are detected during the final evaluation,
+ error logs are created and such services will be disabled.
+ """
+ topic = SystemInfoTopic
+
+ to_enable = fields.List(fields.String(), default=[])
+ """
+ List of systemd services to enable on the target system
+
+ Masked services will not be enabled. Attempting to enable a masked service
+ will be evaluated by systemctl as usually. The error will be logged and the
+ upgrade process will continue.
+ """
+
+ to_disable = fields.List(fields.String(), default=[])
+ """
+ List of systemd services to disable on the target system
+ """
+
+ # NOTE: possible extension in case of requirement (currently not implemented):
+ # to_unmask = fields.List(fields.String(), default=[])
+
+
+class SystemdServiceFile(Model):
+ """
+ Information about single systemd service unit file
+
+ This model is not expected to be produced nor consumed by actors directly.
+ See the :class:`SystemdServicesInfoSource` and :class:`SystemdServicesPresetInfoTarget`
+ for more info.
+ """
+ topic = SystemInfoTopic
+
+ name = fields.String()
+ """
+ Name of the service unit file
+ """
+
+ state = fields.StringEnum([
+ 'alias',
+ 'bad',
+ 'disabled',
+ 'enabled',
+ 'enabled-runtime',
+ 'generated',
+ 'indirect',
+ 'linked',
+ 'linked-runtime',
+ 'masked',
+ 'masked-runtime',
+ 'static',
+ 'transient',
+ ])
+ """
+ The state of the service unit file
+ """
+
+
+class SystemdServicesInfoSource(Model):
+ """
+ Information about systemd services on the source system
+ """
+ topic = SystemInfoTopic
+
+ service_files = fields.List(fields.Model(SystemdServiceFile), default=[])
+ """
+ List of all installed systemd service unit files
+
+ Instances of service template unit files don't have a unit file
+ and therefore aren't included, but their template files are.
+ Generated service unit files are also included.
+ """
+
+
+class SystemdServicesInfoTarget(SystemdServicesInfoSource):
+ """
+ Analogy to :class:`SystemdServicesInfoSource`, but for the target system
+
+ This information is taken after the RPM Upgrade and might become
+ invalid if there are actors calling systemctl enable/disable directly later
+ in the upgrade process. Therefore it is recommended to use
+ :class:`SystemdServicesTasks` to alter the state of units in the
+ FinalizationPhase.
+ """
+
+
+class SystemdServicePreset(Model):
+ """
+ Information about a preset for systemd service
+ """
+
+ topic = SystemInfoTopic
+ service = fields.String()
+ """
+ Name of the service, with the .service suffix
+ """
+
+ state = fields.StringEnum(['disable', 'enable'])
+ """
+ The state set by a preset file
+ """
+
+
+class SystemdServicesPresetInfoSource(Model):
+ """
+ Information about presets for systemd services
+ """
+ topic = SystemInfoTopic
+
+ presets = fields.List(fields.Model(SystemdServicePreset), default=[])
+ """
+ List of all service presets
+ """
+
+
+class SystemdServicesPresetInfoTarget(SystemdServicesPresetInfoSource):
+ """
+ Analogy to :class:`SystemdServicesPresetInfoSource` but for the target system
+ """
diff --git a/repos/system_upgrade/common/models/systemdservices.py b/repos/system_upgrade/common/models/systemdservices.py
deleted file mode 100644
index 6c7d4a1d..00000000
--- a/repos/system_upgrade/common/models/systemdservices.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from leapp.models import fields, Model
-from leapp.topics import SystemInfoTopic
-
-
-class SystemdServicesTasks(Model):
- topic = SystemInfoTopic
-
- to_enable = fields.List(fields.String(), default=[])
- """
- List of systemd services to enable on the target system
-
- Masked services will not be enabled. Attempting to enable a masked service
- will be evaluated by systemctl as usually. The error will be logged and the
- upgrade process will continue.
- """
- to_disable = fields.List(fields.String(), default=[])
- """
- List of systemd services to disable on the target system
- """
-
- # Note: possible extension in case of requirement (currently not implemented):
- # to_unmask = fields.List(fields.String(), default=[])
--
2.38.1