leapp-repository/0056-multipath-Add-MultipathConfCheck9to10-mpath_conf_che.patch
Matej Matuska b31dbfa161 IPU 8.10 -> 9.9: CTC2 candidate 1
- Requires leapp-framework 6.6+
- Initial implementation of upgrades on systems with configured Software RAID
- Handle multipath device identification in upgrade environment
- Fix upgrade incorrectly resuming during SELinux relabeling
- Fix upgrades on systems with multiple LUKS devices
- Fix upgrades for systems with /boot/efi on a Software RAID
- Fix mount failures for FSTAB entries with the `nofail` option specified
- Fix AVC errors triggered by registration to insight-client during the upgrade
- Install the correct kernel for the source system kernel page size on ARM systems
- Inhibit the upgrade when network devices configured with ifcfg files could depend on NM
- Introduce rhui.obsolete_gpg_keys configuration option for the removal of obsoleted RPM GPG keys of RHUI Cloud providers
- LiveMode: Fix upgrade getting stuck when the `.leapp_upgrade_failed` file exists
- LiveMode: Fix system version determination in the upgrade environment on CentOS Stream
- Fix target system initramfs containing outdated configuration files in some circumstances
- Ensure SELinux is set to permissive mode during the upgrade even when enforcing=1 is set on the kernel cmdline
- Fix source and target distribution names in /etc/migration-results
- Fix upgrades with the RealTime kernel on CentOS Stream
- Detect misconfigured kernel & systemd API mountpoints in FSTAB
- Detect misconfigured /var/run on the source system
- Unify behaviour of upgrades on systems with enabled CRB repositories and explicitly required CRB repositories by user during the upgrade
- Drop the inconsistent report about the use of CRB (unsupported by Red Hat) repositories
- Fix upgrade crashing when dnf repofiles contain URL encoded characters

- Resolves: RHEL-3289, RHEL-17842, RHEL-36249, RHEL-56040, RHEL-56176, RHEL-60060, RHEL-76846, RHEL-104384, RHEL-145136, RHEL-148631, RHEL-162192, RHEL-185508
2026-06-29 13:26:50 +02:00

523 lines
20 KiB
Diff

From 29de17c95fe77ebd4da83f55d12ef294ecd29544 Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
Date: Tue, 24 Mar 2026 17:31:26 -0400
Subject: [PATCH 056/108] multipath: Add MultipathConfCheck9to10
mpath_conf_check actor
Add the el9toel10 mpath_conf_check actor that consumes
MultipathConfFacts9to10 and reports on multipath configuration changes
needed for the RHEL 9 to RHEL 10 upgrade: deprecated config_dir,
deprecated file paths (bindings/wwids/prkeys), socket activation
disabled by default, unsupported DM NVMe multipathing, and
getuid_callout as an upgrade inhibitor.
Also, if config_dir is not /etc/multipath/conf.d, the upgrade will be
inhibited if /etc/multipath/conf.d already exists and has config files
in it. Otherwise they would become part of the new config, after the
upgrade.
Jira: RHEL-151509
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---
.../multipath/mpath_conf_check/actor.py | 25 ++
.../libraries/mpath_conf_check.py | 188 +++++++++++++
.../tests/test_multipath_conf_check_9to10.py | 258 ++++++++++++++++++
3 files changed, 471 insertions(+)
create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/actor.py
create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/libraries/mpath_conf_check.py
create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/tests/test_multipath_conf_check_9to10.py
diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/actor.py b/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/actor.py
new file mode 100644
index 00000000..20feb767
--- /dev/null
+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/actor.py
@@ -0,0 +1,25 @@
+from leapp.actors import Actor
+from leapp.libraries.actor import mpath_conf_check
+from leapp.models import MultipathConfFacts9to10
+from leapp.reporting import Report
+from leapp.tags import ChecksPhaseTag, IPUWorkflowTag
+
+
+class MultipathConfCheck9to10(Actor):
+ """
+ Checks if changes to the multipath configuration files are necessary
+ for upgrading to RHEL10, and reports the results.
+ """
+
+ name = 'multipath_conf_check_9to10'
+ consumes = (MultipathConfFacts9to10,)
+ produces = (Report,)
+ tags = (ChecksPhaseTag, IPUWorkflowTag)
+
+ def process(self):
+ facts = next(self.consume(MultipathConfFacts9to10), None)
+ if facts is None:
+ self.log.debug('Skipping execution. No MultipathConfFacts9to10 has '
+ 'been produced')
+ return
+ mpath_conf_check.check_configs(facts)
diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/libraries/mpath_conf_check.py b/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/libraries/mpath_conf_check.py
new file mode 100644
index 00000000..49aefc5a
--- /dev/null
+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/libraries/mpath_conf_check.py
@@ -0,0 +1,188 @@
+import os
+
+from leapp import reporting
+from leapp.reporting import create_report
+
+_DEFAULT_CONFIG_DIR = '/etc/multipath/conf.d'
+_DEFAULT_BINDINGS_FILE = '/etc/multipath/bindings'
+_DEFAULT_WWIDS_FILE = '/etc/multipath/wwids'
+_DEFAULT_PRKEYS_FILE = '/etc/multipath/prkeys'
+
+
+def _default_config_dir_has_conf_files():
+ if not os.path.exists(_DEFAULT_CONFIG_DIR):
+ return False
+ for filename in os.listdir(_DEFAULT_CONFIG_DIR):
+ if filename.endswith('.conf'):
+ return True
+ return False
+
+
+def _report_config_dir(config_dir):
+ create_report([
+ reporting.Title(
+ 'device-mapper-multipath custom config_dir is deprecated'
+ ),
+ reporting.Summary(
+ 'The multipath configuration option "config_dir" is set to '
+ '"{cfg_dir}". In RHEL-10, this option is deprecated and unused. '
+ 'The only valid configuration directory is "{def_cfg_dir}". Any '
+ 'configuration files in "{cfg_dir}" will be moved to '
+ '"{def_cfg_dir}".'.format(
+ cfg_dir=config_dir, def_cfg_dir=_DEFAULT_CONFIG_DIR)),
+ reporting.Severity(reporting.Severity.INFO),
+ reporting.Groups([reporting.Groups.SERVICES]),
+ reporting.RelatedResource('package', 'device-mapper-multipath')
+ ])
+
+
+def _report_config_dir_conflict(config_dir):
+ create_report([
+ reporting.Title(
+ 'device-mapper-multipath config_dir conflict'
+ ),
+ reporting.Summary(
+ 'The multipath configuration option "config_dir" is set to '
+ '"{cfg_dir}". During the upgrade, configuration files from '
+ '"{cfg_dir}" will be moved to "{def_cfg_dir}". However, '
+ '"{def_cfg_dir}" already contains .conf files. These existing '
+ 'files would be added to the multipath configuration after the '
+ 'upgrade. Please remove or relocate the files in "{def_cfg_dir}" '
+ 'before upgrading.'.format(
+ cfg_dir=config_dir, def_cfg_dir=_DEFAULT_CONFIG_DIR)),
+ reporting.Severity(reporting.Severity.HIGH),
+ reporting.Groups([reporting.Groups.INHIBITOR]),
+ reporting.RelatedResource('package', 'device-mapper-multipath')
+ ])
+
+
+def _report_files(file_list):
+ details = ', '.join(
+ '{} (currently "{}") will be moved to "{}"'.format(name, current, default)
+ for name, current, default in file_list
+ )
+ create_report([
+ reporting.Title(
+ 'device-mapper-multipath configuration files will be moved'
+ ),
+ reporting.Summary(
+ 'The following multipath configuration file locations are '
+ 'deprecated and unused in RHEL-10. The files will be moved '
+ 'to their default locations: {}.'.format(details)),
+ reporting.Severity(reporting.Severity.INFO),
+ reporting.Groups([reporting.Groups.SERVICES]),
+ reporting.RelatedResource('package', 'device-mapper-multipath')
+ ])
+
+
+def _report_socket_activation():
+ create_report([
+ reporting.Title(
+ 'device-mapper-multipath socket activation is disabled by default'
+ ),
+ reporting.Summary(
+ 'In RHEL-10, multipathd socket activation is disabled by '
+ 'default. If you wish to re-enable it, uncomment '
+ '"WantedBy=sockets.target" in '
+ '/lib/systemd/system/multipathd.socket'),
+ reporting.Severity(reporting.Severity.INFO),
+ reporting.Groups([reporting.Groups.SERVICES]),
+ reporting.RelatedResource('package', 'device-mapper-multipath')
+ ])
+
+
+def _report_dm_nvme_multipathing():
+ create_report([
+ reporting.Title(
+ 'device-mapper-multipath NVMe multipathing is no longer supported'
+ ),
+ reporting.Summary(
+ 'Only Native NVMe multipathing is supported in RHEL-10. Any '
+ 'multipath NVMe devices will still work, but they will no '
+ 'longer be managed by dm-multipath.'),
+ reporting.Severity(reporting.Severity.INFO),
+ reporting.Groups([reporting.Groups.SERVICES]),
+ reporting.RelatedResource('package', 'device-mapper-multipath')
+ ])
+
+
+def _create_paths_str(paths):
+ if len(paths) < 2:
+ return paths[0]
+ return '{} and {}'.format(', '.join(paths[0:-1]), paths[-1])
+
+
+def _report_getuid(paths):
+ paths_str = _create_paths_str(paths)
+ create_report([
+ reporting.Title(
+ 'device-mapper-multipath configuration contains getuid_callout'
+ ),
+ reporting.Summary(
+ 'The "getuid_callout" option is no longer supported in '
+ 'RHEL-10. It must be removed from the multipath '
+ 'configuration before upgrading. The option was found in '
+ '{}.'.format(paths_str)),
+ reporting.Severity(reporting.Severity.HIGH),
+ reporting.Groups([reporting.Groups.INHIBITOR]),
+ reporting.RelatedResource('package', 'device-mapper-multipath')
+ ])
+
+
+def check_configs(facts):
+ if not facts.configs:
+ return
+
+ primary = facts.configs[0]
+
+ # config_dir: only valid in primary config
+ config_dir = primary.config_dir
+ if (
+ config_dir is not None and
+ os.path.normpath(config_dir) != _DEFAULT_CONFIG_DIR
+ ):
+ _report_config_dir(config_dir)
+ if _default_config_dir_has_conf_files():
+ _report_config_dir_conflict(config_dir)
+
+ # bindings_file, wwids_file, prkeys_file: last non-None value wins
+ bindings_file = None
+ wwids_file = None
+ prkeys_file = None
+ for conf in facts.configs:
+ if conf.bindings_file is not None:
+ bindings_file = conf.bindings_file
+ if conf.wwids_file is not None:
+ wwids_file = conf.wwids_file
+ if conf.prkeys_file is not None:
+ prkeys_file = conf.prkeys_file
+
+ file_list = []
+ if (
+ bindings_file is not None and
+ os.path.normpath(bindings_file) != _DEFAULT_BINDINGS_FILE
+ ):
+ file_list.append(('bindings_file', bindings_file, _DEFAULT_BINDINGS_FILE))
+ if (
+ wwids_file is not None and
+ os.path.normpath(wwids_file) != _DEFAULT_WWIDS_FILE
+ ):
+ file_list.append(('wwids_file', wwids_file, _DEFAULT_WWIDS_FILE))
+ if (
+ prkeys_file is not None and
+ os.path.normpath(prkeys_file) != _DEFAULT_PRKEYS_FILE
+ ):
+ file_list.append(('prkeys_file', prkeys_file, _DEFAULT_PRKEYS_FILE))
+ if file_list:
+ _report_files(file_list)
+
+ # socket activation and dm nvme multipathing: system-level, primary only
+ if primary.has_socket_activation:
+ _report_socket_activation()
+ if primary.has_dm_nvme_multipathing:
+ _report_dm_nvme_multipathing()
+
+ # getuid: per-file, report if any config has it
+ getuid_paths = [conf.pathname for conf in facts.configs if conf.has_getuid]
+ if getuid_paths:
+ _report_getuid(getuid_paths)
diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/tests/test_multipath_conf_check_9to10.py b/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/tests/test_multipath_conf_check_9to10.py
new file mode 100644
index 00000000..bb03e5dd
--- /dev/null
+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_conf_check/tests/test_multipath_conf_check_9to10.py
@@ -0,0 +1,258 @@
+from leapp.libraries.actor import mpath_conf_check
+from leapp.models import MultipathConfFacts9to10, MultipathConfig9to10
+from leapp.reporting import Report
+
+
+def _assert_config_dir_report(report):
+ assert report['title'] == \
+ 'device-mapper-multipath custom config_dir is deprecated'
+ assert report['severity'] == 'info'
+
+
+def _assert_config_dir_conflict_report(report):
+ assert report['title'] == \
+ 'device-mapper-multipath config_dir conflict'
+ assert report['severity'] == 'high'
+
+
+def _assert_files_report(report):
+ assert report['title'] == \
+ 'device-mapper-multipath configuration files will be moved'
+ assert report['severity'] == 'info'
+
+
+def _assert_socket_activation_report(report):
+ assert report['title'] == \
+ 'device-mapper-multipath socket activation is disabled by default'
+ assert report['severity'] == 'info'
+
+
+def _assert_dm_nvme_report(report):
+ assert report['title'] == \
+ 'device-mapper-multipath NVMe multipathing is no longer supported'
+ assert report['severity'] == 'info'
+
+
+def _assert_getuid_report(report, paths_str):
+ assert report['title'] == \
+ 'device-mapper-multipath configuration contains getuid_callout'
+ assert report['severity'] == 'high'
+ assert paths_str in report['summary']
+
+
+def _build_config(pathname, config_dir=None, bindings_file=None,
+ wwids_file=None, prkeys_file=None,
+ has_socket_activation=False, has_dm_nvme_multipathing=False,
+ has_getuid=False):
+ return MultipathConfig9to10(
+ pathname=pathname,
+ config_dir=config_dir,
+ bindings_file=bindings_file,
+ wwids_file=wwids_file,
+ prkeys_file=prkeys_file,
+ has_socket_activation=has_socket_activation,
+ has_dm_nvme_multipathing=has_dm_nvme_multipathing,
+ has_getuid=has_getuid,
+ )
+
+
+def _build_facts(confs):
+ return MultipathConfFacts9to10(configs=confs)
+
+
+def test_no_issues(current_actor_context):
+ config = _build_config('no_issues.conf')
+ facts = _build_facts([config])
+ current_actor_context.feed(facts)
+ current_actor_context.run()
+ reports = current_actor_context.consume(Report)
+ assert not reports
+
+
+def test_all_issues(current_actor_context, monkeypatch):
+ monkeypatch.setattr(mpath_conf_check, '_default_config_dir_has_conf_files', lambda: False)
+ config = _build_config(
+ 'all_issues.conf',
+ config_dir='/etc/multipath/foo.d',
+ bindings_file='/tmp/bindings',
+ has_socket_activation=True,
+ has_dm_nvme_multipathing=True,
+ has_getuid=True)
+ facts = _build_facts([config])
+ current_actor_context.feed(facts)
+ current_actor_context.run()
+ reports = list(current_actor_context.consume(Report))
+ assert reports and len(reports) == 5
+ _assert_config_dir_report(reports[0].report)
+ _assert_files_report(reports[1].report)
+ _assert_socket_activation_report(reports[2].report)
+ _assert_dm_nvme_report(reports[3].report)
+ _assert_getuid_report(reports[4].report, 'all_issues.conf')
+
+
+def test_config_dir_default(current_actor_context):
+ config = _build_config('default_dir.conf',
+ config_dir='/etc/multipath/conf.d')
+ facts = _build_facts([config])
+ current_actor_context.feed(facts)
+ current_actor_context.run()
+ reports = current_actor_context.consume(Report)
+ assert not reports
+
+
+def test_config_dir_nondefault(current_actor_context, monkeypatch):
+ monkeypatch.setattr(mpath_conf_check, '_default_config_dir_has_conf_files', lambda: False)
+ config = _build_config('custom_dir.conf',
+ config_dir='/etc/multipath/foo.d')
+ facts = _build_facts([config])
+ current_actor_context.feed(facts)
+ current_actor_context.run()
+ reports = list(current_actor_context.consume(Report))
+ assert reports and len(reports) == 1
+ _assert_config_dir_report(reports[0].report)
+
+
+def test_files_nondefault(current_actor_context):
+ config = _build_config('custom_files.conf',
+ bindings_file='/tmp/bindings',
+ wwids_file='/tmp/wwids')
+ facts = _build_facts([config])
+ current_actor_context.feed(facts)
+ current_actor_context.run()
+ reports = list(current_actor_context.consume(Report))
+ assert reports and len(reports) == 1
+ _assert_files_report(reports[0].report)
+ assert '/tmp/bindings' in reports[0].report['summary']
+ assert '/tmp/wwids' in reports[0].report['summary']
+
+
+def test_files_overridden_by_secondary(current_actor_context):
+ primary = _build_config('primary.conf',
+ bindings_file='/tmp/bindings')
+ secondary = _build_config('secondary.conf',
+ bindings_file='/etc/multipath/bindings')
+ facts = _build_facts([primary, secondary])
+ current_actor_context.feed(facts)
+ current_actor_context.run()
+ reports = current_actor_context.consume(Report)
+ assert not reports
+
+
+def test_files_secondary_overrides_to_nondefault(current_actor_context):
+ primary = _build_config('primary.conf')
+ secondary = _build_config('secondary.conf',
+ bindings_file='/tmp/bindings')
+ facts = _build_facts([primary, secondary])
+ current_actor_context.feed(facts)
+ current_actor_context.run()
+ reports = list(current_actor_context.consume(Report))
+ assert reports and len(reports) == 1
+ _assert_files_report(reports[0].report)
+
+
+def test_socket_activation(current_actor_context):
+ config = _build_config('socket.conf', has_socket_activation=True)
+ facts = _build_facts([config])
+ current_actor_context.feed(facts)
+ current_actor_context.run()
+ reports = list(current_actor_context.consume(Report))
+ assert reports and len(reports) == 1
+ _assert_socket_activation_report(reports[0].report)
+
+
+def test_no_socket_activation(current_actor_context):
+ config = _build_config('no_socket.conf', has_socket_activation=False)
+ facts = _build_facts([config])
+ current_actor_context.feed(facts)
+ current_actor_context.run()
+ reports = current_actor_context.consume(Report)
+ assert not reports
+
+
+def test_dm_nvme(current_actor_context):
+ config = _build_config('nvme.conf', has_dm_nvme_multipathing=True)
+ facts = _build_facts([config])
+ current_actor_context.feed(facts)
+ current_actor_context.run()
+ reports = list(current_actor_context.consume(Report))
+ assert reports and len(reports) == 1
+ _assert_dm_nvme_report(reports[0].report)
+
+
+def test_getuid_inhibitor(current_actor_context):
+ config = _build_config('getuid.conf', has_getuid=True)
+ facts = _build_facts([config])
+ current_actor_context.feed(facts)
+ current_actor_context.run()
+ reports = list(current_actor_context.consume(Report))
+ assert reports and len(reports) == 1
+ _assert_getuid_report(reports[0].report, 'getuid.conf')
+
+
+def test_getuid_in_secondary(current_actor_context):
+ primary = _build_config('primary.conf')
+ secondary = _build_config('secondary.conf', has_getuid=True)
+ facts = _build_facts([primary, secondary])
+ current_actor_context.feed(facts)
+ current_actor_context.run()
+ reports = list(current_actor_context.consume(Report))
+ assert reports and len(reports) == 1
+ _assert_getuid_report(reports[0].report, 'secondary.conf')
+
+
+def test_multiple_secondaries(current_actor_context):
+ # primary: non-default bindings, no getuid
+ primary = _build_config('primary.conf',
+ bindings_file='/tmp/bindings')
+ # second1: overrides bindings back to default, sets non-default wwids,
+ # has getuid
+ second1 = _build_config('second1.conf',
+ bindings_file='/etc/multipath/bindings',
+ wwids_file='/tmp/wwids',
+ has_getuid=True)
+ # second2: overrides wwids back to default, sets non-default prkeys,
+ # no getuid
+ second2 = _build_config('second2.conf',
+ wwids_file='/etc/multipath/wwids',
+ prkeys_file='/tmp/prkeys')
+ # second3: has getuid only
+ second3 = _build_config('second3.conf', has_getuid=True)
+ facts = _build_facts([primary, second1, second2, second3])
+ current_actor_context.feed(facts)
+ current_actor_context.run()
+ reports = list(current_actor_context.consume(Report))
+ # Expect: files report (only prkeys non-default) + getuid inhibitor
+ assert reports and len(reports) == 2
+ _assert_files_report(reports[0].report)
+ # bindings was overridden to default, wwids was overridden to default,
+ # only prkeys should appear
+ assert '/tmp/prkeys' in reports[0].report['summary']
+ assert '/tmp/bindings' not in reports[0].report['summary']
+ assert '/tmp/wwids' not in reports[0].report['summary']
+ # getuid found in second1 and second3
+ _assert_getuid_report(reports[1].report, 'second1.conf and second3.conf')
+
+
+def test_config_dir_conflict_inhibitor(current_actor_context, monkeypatch):
+ monkeypatch.setattr(mpath_conf_check, '_default_config_dir_has_conf_files', lambda: True)
+ config = _build_config('custom_dir.conf',
+ config_dir='/etc/multipath/foo.d')
+ facts = _build_facts([config])
+ current_actor_context.feed(facts)
+ current_actor_context.run()
+ reports = list(current_actor_context.consume(Report))
+ assert reports and len(reports) == 2
+ _assert_config_dir_report(reports[0].report)
+ _assert_config_dir_conflict_report(reports[1].report)
+
+
+def test_config_dir_no_conflict(current_actor_context, monkeypatch):
+ monkeypatch.setattr(mpath_conf_check, '_default_config_dir_has_conf_files', lambda: False)
+ config = _build_config('custom_dir.conf',
+ config_dir='/etc/multipath/foo.d')
+ facts = _build_facts([config])
+ current_actor_context.feed(facts)
+ current_actor_context.run()
+ reports = list(current_actor_context.consume(Report))
+ assert reports and len(reports) == 1
+ _assert_config_dir_report(reports[0].report)
--
2.54.0