leapp-repository/0057-multipath-Add-MultipathUpgradeConfUpdate9to10-mpath_.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

660 lines
29 KiB
Diff

From beffa9b5decef2639af93adf014ca63071900e3e Mon Sep 17 00:00:00 2001
From: Benjamin Marzinski <bmarzins@redhat.com>
Date: Mon, 30 Mar 2026 18:39:57 -0400
Subject: [PATCH 057/108] multipath: Add MultipathUpgradeConfUpdate9to10
mpath_upgrade_conf_patcher actor
Removes deprecated defaults section options (config_dir, bindings_file,
wwids_file, prkeys_file) from multipath configuration files during the
RHEL 9 to RHEL 10 upgrade. Relocates secondary configs to
/etc/multipath/conf.d/ when config_dir is non-default, and creates
entries to move bindings, wwids, and prkeys files to their default
RHEL-10 locations.
Also make multipath_system_config_patcher create directories if they
don't already exist when copying files, to deal with the case where
config files need to be copied to /etc/multipath/conf.d, but it doesn't
exist.
Jira: RHEL-151509
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---
.../libraries/system_config_patcher.py | 5 +
.../mpath_upgrade_conf_patcher/actor.py | 29 ++
.../libraries/mpathconfupdate.py | 143 ++++++++++
.../tests/files/after/all_deprecated.conf | 7 +
.../tests/files/after/has_config_dir.conf | 4 +
.../tests/files/after/has_files.conf | 6 +
.../after/secondary_with_deprecated.conf | 9 +
.../tests/files/before/all_deprecated.conf | 7 +
.../tests/files/before/empty.conf | 1 +
.../tests/files/before/has_config_dir.conf | 4 +
.../tests/files/before/has_files.conf | 6 +
.../tests/files/before/no_defaults.conf | 3 +
.../tests/files/before/no_deprecated.conf | 3 +
.../tests/files/before/secondary_simple.conf | 6 +
.../before/secondary_with_deprecated.conf | 9 +
.../tests/test_mpath_conf_update_9to10.py | 256 ++++++++++++++++++
16 files changed, 498 insertions(+)
create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/actor.py
create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/libraries/mpathconfupdate.py
create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/all_deprecated.conf
create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/has_config_dir.conf
create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/has_files.conf
create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/secondary_with_deprecated.conf
create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/all_deprecated.conf
create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/empty.conf
create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/has_config_dir.conf
create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/has_files.conf
create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/no_defaults.conf
create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/no_deprecated.conf
create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/secondary_simple.conf
create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/secondary_with_deprecated.conf
create mode 100644 repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/test_mpath_conf_update_9to10.py
diff --git a/repos/system_upgrade/common/actors/multipath/system_conf_patcher/libraries/system_config_patcher.py b/repos/system_upgrade/common/actors/multipath/system_conf_patcher/libraries/system_config_patcher.py
index 0d873322..0d1fd070 100644
--- a/repos/system_upgrade/common/actors/multipath/system_conf_patcher/libraries/system_config_patcher.py
+++ b/repos/system_upgrade/common/actors/multipath/system_conf_patcher/libraries/system_config_patcher.py
@@ -1,3 +1,4 @@
+import os
import shutil
from leapp.libraries.stdlib import api
@@ -14,4 +15,8 @@ def patch_system_configs():
)
)
+ os.makedirs(
+ os.path.dirname(modified_config.target_path),
+ exist_ok=True
+ )
shutil.copy(modified_config.updated_config_location, modified_config.target_path)
diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/actor.py b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/actor.py
new file mode 100644
index 00000000..f72d2936
--- /dev/null
+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/actor.py
@@ -0,0 +1,29 @@
+from leapp.actors import Actor
+from leapp.libraries.actor import mpathconfupdate
+from leapp.models import MultipathConfFacts9to10, MultipathConfigUpdatesInfo
+from leapp.tags import IPUWorkflowTag, TargetTransactionChecksPhaseTag
+
+
+class MultipathUpgradeConfUpdate9to10(Actor):
+ """
+ Modifies multipath configuration files for the RHEL-10 upgrade.
+
+ Removes deprecated options (config_dir, bindings_file, wwids_file,
+ prkeys_file) from multipath configuration files. If config_dir is
+ set to a non-default directory, ensures all secondary configs are
+ moved to /etc/multipath/conf.d/. Creates entries to relocate
+ bindings, wwids, and prkeys files to their default RHEL-10
+ locations if necessary.
+ """
+
+ name = 'multipath_upgrade_conf_update_9to10'
+ consumes = (MultipathConfFacts9to10,)
+ produces = (MultipathConfigUpdatesInfo,)
+ tags = (TargetTransactionChecksPhaseTag, 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
+ mpathconfupdate.update_configs(facts)
diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/libraries/mpathconfupdate.py b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/libraries/mpathconfupdate.py
new file mode 100644
index 00000000..a2e3bafc
--- /dev/null
+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/libraries/mpathconfupdate.py
@@ -0,0 +1,143 @@
+import os
+import shutil
+
+from leapp.libraries.common import multipathutil
+from leapp.libraries.stdlib import api
+from leapp.models import MultipathConfigUpdatesInfo, UpdatedMultipathConfig
+
+MODIFICATIONS_STORE_PATH = '/var/lib/leapp/proposed_modifications'
+
+_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'
+
+_deprecated_options = ('config_dir', 'bindings_file', 'wwids_file', 'prkeys_file')
+
+
+def _update_config(config):
+ contents = multipathutil.read_config(config.pathname)
+ if contents is None:
+ return None
+ lines = contents.split('\n')
+
+ section = None
+ in_subsection = False
+ updated_file = False
+ comment_lines = []
+ for i, line in enumerate(lines):
+ try:
+ data = multipathutil.LineData(line, section, in_subsection)
+ except ValueError:
+ continue
+ if data.type == data.TYPE_SECTION_END:
+ if in_subsection:
+ in_subsection = False
+ elif section is not None:
+ section = None
+ elif data.type == data.TYPE_SECTION_START:
+ if section is None:
+ section = data.section
+ elif not in_subsection:
+ in_subsection = True
+ elif data.type == data.TYPE_OPTION:
+ if section == 'defaults' and data.option in _deprecated_options:
+ comment_lines.append(i)
+ updated_file = True
+
+ if not updated_file:
+ return None
+
+ for i in reversed(comment_lines):
+ lines[i] = '#{} # line commented out by leapp'.format(lines[i])
+
+ return '\n'.join(lines)
+
+
+def _get_file_locations(facts):
+ bindings_file = None
+ wwids_file = None
+ prkeys_file = None
+ for conf in facts.configs:
+ if conf.bindings_file is not None:
+ bindings_file = os.path.normpath(conf.bindings_file)
+ if conf.wwids_file is not None:
+ wwids_file = os.path.normpath(conf.wwids_file)
+ if conf.prkeys_file is not None:
+ prkeys_file = os.path.normpath(conf.prkeys_file)
+
+ file_updates = []
+ if bindings_file is not None and bindings_file != _DEFAULT_BINDINGS_FILE:
+ file_updates.append((bindings_file, _DEFAULT_BINDINGS_FILE))
+ if wwids_file is not None and wwids_file != _DEFAULT_WWIDS_FILE:
+ file_updates.append((wwids_file, _DEFAULT_WWIDS_FILE))
+ if prkeys_file is not None and prkeys_file != _DEFAULT_PRKEYS_FILE:
+ file_updates.append((prkeys_file, _DEFAULT_PRKEYS_FILE))
+ return file_updates
+
+
+def prepare_destination_for_file(file_path):
+ dirname = os.path.dirname(file_path)
+ os.makedirs(dirname, exist_ok=True)
+
+
+def prepare_place_for_config_modifications(workspace_path=MODIFICATIONS_STORE_PATH):
+ if os.path.exists(workspace_path):
+ shutil.rmtree(workspace_path)
+ os.mkdir(workspace_path)
+
+
+def update_configs(facts):
+ if not facts.configs:
+ return
+
+ config_updates = []
+ prepare_place_for_config_modifications()
+
+ primary = facts.configs[0]
+ non_default_config_dir = (
+ primary.config_dir is not None
+ and os.path.normpath(primary.config_dir) != _DEFAULT_CONFIG_DIR
+ )
+
+ for idx, config in enumerate(facts.configs):
+ is_secondary = idx > 0
+
+ if is_secondary:
+ target_path = os.path.join(
+ _DEFAULT_CONFIG_DIR, os.path.basename(config.pathname)
+ )
+ else:
+ target_path = config.pathname
+
+ contents = _update_config(config)
+
+ if contents is not None:
+ rootless_path = config.pathname.lstrip('/')
+ updated_config_location = os.path.join(
+ MODIFICATIONS_STORE_PATH, rootless_path
+ )
+ api.current_logger().debug(
+ 'Instead of modifying {}, preparing modified config at {}'.format(
+ config.pathname, updated_config_location
+ )
+ )
+ prepare_destination_for_file(updated_config_location)
+ multipathutil.write_config(updated_config_location, contents)
+ config_updates.append(UpdatedMultipathConfig(
+ updated_config_location=updated_config_location,
+ target_path=target_path
+ ))
+ elif is_secondary and non_default_config_dir:
+ config_updates.append(UpdatedMultipathConfig(
+ updated_config_location=config.pathname,
+ target_path=target_path
+ ))
+
+ for source_path, default_path in _get_file_locations(facts):
+ config_updates.append(UpdatedMultipathConfig(
+ updated_config_location=source_path,
+ target_path=default_path
+ ))
+
+ api.produce(MultipathConfigUpdatesInfo(updates=config_updates))
diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/all_deprecated.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/all_deprecated.conf
new file mode 100644
index 00000000..7489ed9e
--- /dev/null
+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/all_deprecated.conf
@@ -0,0 +1,7 @@
+defaults {
+ polling_interval 5
+# config_dir "/etc/multipath/custom.d" # line commented out by leapp
+# bindings_file "/tmp/bindings" # line commented out by leapp
+# wwids_file "/tmp/wwids" # line commented out by leapp
+# prkeys_file "/tmp/prkeys" # line commented out by leapp
+}
diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/has_config_dir.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/has_config_dir.conf
new file mode 100644
index 00000000..94c54ded
--- /dev/null
+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/has_config_dir.conf
@@ -0,0 +1,4 @@
+defaults {
+ polling_interval 5
+# config_dir "/etc/multipath/custom.d" # line commented out by leapp
+}
diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/has_files.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/has_files.conf
new file mode 100644
index 00000000..fb74a86e
--- /dev/null
+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/has_files.conf
@@ -0,0 +1,6 @@
+defaults {
+ polling_interval 5
+# bindings_file "/tmp/bindings" # line commented out by leapp
+# wwids_file "/tmp/wwids" # line commented out by leapp
+# prkeys_file "/tmp/prkeys" # line commented out by leapp
+}
diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/secondary_with_deprecated.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/secondary_with_deprecated.conf
new file mode 100644
index 00000000..68bfcfca
--- /dev/null
+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/after/secondary_with_deprecated.conf
@@ -0,0 +1,9 @@
+defaults {
+# bindings_file "/tmp/bindings" # line commented out by leapp
+}
+devices {
+ device {
+ vendor "VENDOR"
+ product "PRODUCT"
+ }
+}
diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/all_deprecated.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/all_deprecated.conf
new file mode 100644
index 00000000..15e39d34
--- /dev/null
+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/all_deprecated.conf
@@ -0,0 +1,7 @@
+defaults {
+ polling_interval 5
+ config_dir "/etc/multipath/custom.d"
+ bindings_file "/tmp/bindings"
+ wwids_file "/tmp/wwids"
+ prkeys_file "/tmp/prkeys"
+}
diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/empty.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/empty.conf
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/empty.conf
@@ -0,0 +1 @@
+
diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/has_config_dir.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/has_config_dir.conf
new file mode 100644
index 00000000..7e8295ee
--- /dev/null
+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/has_config_dir.conf
@@ -0,0 +1,4 @@
+defaults {
+ polling_interval 5
+ config_dir "/etc/multipath/custom.d"
+}
diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/has_files.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/has_files.conf
new file mode 100644
index 00000000..9404312b
--- /dev/null
+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/has_files.conf
@@ -0,0 +1,6 @@
+defaults {
+ polling_interval 5
+ bindings_file "/tmp/bindings"
+ wwids_file "/tmp/wwids"
+ prkeys_file "/tmp/prkeys"
+}
diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/no_defaults.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/no_defaults.conf
new file mode 100644
index 00000000..dfc28f43
--- /dev/null
+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/no_defaults.conf
@@ -0,0 +1,3 @@
+blacklist {
+ devnode "^sd[a-z]"
+}
diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/no_deprecated.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/no_deprecated.conf
new file mode 100644
index 00000000..8c0e1510
--- /dev/null
+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/no_deprecated.conf
@@ -0,0 +1,3 @@
+defaults {
+ polling_interval 5
+}
diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/secondary_simple.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/secondary_simple.conf
new file mode 100644
index 00000000..092e7cdc
--- /dev/null
+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/secondary_simple.conf
@@ -0,0 +1,6 @@
+devices {
+ device {
+ vendor "VENDOR"
+ product "PRODUCT"
+ }
+}
diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/secondary_with_deprecated.conf b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/secondary_with_deprecated.conf
new file mode 100644
index 00000000..534c6f07
--- /dev/null
+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/files/before/secondary_with_deprecated.conf
@@ -0,0 +1,9 @@
+defaults {
+ bindings_file "/tmp/bindings"
+}
+devices {
+ device {
+ vendor "VENDOR"
+ product "PRODUCT"
+ }
+}
diff --git a/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/test_mpath_conf_update_9to10.py b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/test_mpath_conf_update_9to10.py
new file mode 100644
index 00000000..ad6668b9
--- /dev/null
+++ b/repos/system_upgrade/el9toel10/actors/multipath/mpath_upgrade_conf_patcher/tests/test_mpath_conf_update_9to10.py
@@ -0,0 +1,256 @@
+import os
+
+import pytest
+
+from leapp.libraries.actor import mpathconfupdate
+from leapp.libraries.common import multipathutil
+from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked
+from leapp.libraries.stdlib import api
+from leapp.models import MultipathConfFacts9to10, MultipathConfig9to10
+
+BEFORE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'files/before')
+AFTER_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'files/after')
+
+
+def build_config(pathname, config_dir=None, bindings_file=None,
+ wwids_file=None, prkeys_file=None):
+ return MultipathConfig9to10(
+ pathname=pathname,
+ config_dir=config_dir,
+ bindings_file=bindings_file,
+ wwids_file=wwids_file,
+ prkeys_file=prkeys_file,
+ )
+
+
+def build_facts(confs):
+ return MultipathConfFacts9to10(configs=confs)
+
+
+def mock_read_config(path):
+ return multipathutil.read_config_orig(os.path.join(BEFORE_DIR, path))
+
+
+# Configs with no changes needed
+no_deprecated_conf = build_config('no_deprecated.conf')
+empty_conf = build_config('empty.conf')
+no_defaults_conf = build_config('no_defaults.conf')
+
+# Configs with deprecated options
+has_config_dir_conf = build_config(
+ 'has_config_dir.conf', config_dir='/etc/multipath/custom.d')
+has_files_conf = build_config(
+ 'has_files.conf', bindings_file='/tmp/bindings',
+ wwids_file='/tmp/wwids', prkeys_file='/tmp/prkeys')
+all_deprecated_conf = build_config(
+ 'all_deprecated.conf', config_dir='/etc/multipath/custom.d',
+ bindings_file='/tmp/bindings', wwids_file='/tmp/wwids',
+ prkeys_file='/tmp/prkeys')
+
+# Secondary configs
+secondary_simple_conf = build_config('secondary_simple.conf')
+secondary_with_deprecated_conf = build_config(
+ 'secondary_with_deprecated.conf', bindings_file='/tmp/bindings')
+
+
+@pytest.mark.parametrize(
+ 'config_facts',
+ [
+ build_facts([no_deprecated_conf]),
+ build_facts([empty_conf]),
+ build_facts([no_defaults_conf]),
+ build_facts([has_config_dir_conf]),
+ build_facts([has_files_conf]),
+ build_facts([all_deprecated_conf]),
+ build_facts([has_config_dir_conf, secondary_simple_conf]),
+ build_facts([has_config_dir_conf, secondary_with_deprecated_conf]),
+ build_facts([no_deprecated_conf, secondary_simple_conf]),
+ build_facts([no_deprecated_conf, secondary_with_deprecated_conf]),
+ ]
+)
+def test_all_facts(monkeypatch, config_facts):
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked())
+
+ produce_mock = produce_mocked()
+ monkeypatch.setattr(api, 'produce', produce_mock)
+
+ config_writes = {}
+
+ def write_config_mock(location, contents):
+ config_writes[location] = contents
+
+ monkeypatch.setattr(multipathutil, 'read_config_orig', multipathutil.read_config, raising=False)
+ monkeypatch.setattr(multipathutil, 'read_config', mock_read_config)
+ monkeypatch.setattr(multipathutil, 'write_config', write_config_mock)
+ monkeypatch.setattr(mpathconfupdate, 'prepare_destination_for_file', lambda file_path: None)
+ monkeypatch.setattr(mpathconfupdate, 'prepare_place_for_config_modifications', lambda: None)
+
+ mpathconfupdate.update_configs(config_facts)
+
+ config_updates = {}
+ for config_updates_msg in produce_mock.model_instances:
+ for update in config_updates_msg.updates:
+ config_updates[update.target_path] = update.updated_config_location
+
+ primary = config_facts.configs[0]
+ non_default_config_dir = (
+ primary.config_dir is not None
+ and os.path.normpath(primary.config_dir) != '/etc/multipath/conf.d'
+ )
+
+ for idx, config in enumerate(config_facts.configs):
+ is_secondary = idx > 0
+
+ if is_secondary:
+ target_path = os.path.join(
+ '/etc/multipath/conf.d', os.path.basename(config.pathname)
+ )
+ else:
+ target_path = config.pathname
+
+ expected_conf_location = os.path.join(AFTER_DIR, config.pathname)
+
+ if target_path not in config_updates:
+ # No update for this config - verify no expected after file exists
+ # and it's not a secondary that should have been relocated
+ assert not os.path.exists(expected_conf_location)
+ assert not (is_secondary and non_default_config_dir)
+ continue
+
+ updated_config_location = config_updates[target_path]
+
+ if os.path.exists(expected_conf_location):
+ # Config was modified - check contents
+ assert updated_config_location in config_writes
+ actual_contents = config_writes[updated_config_location]
+
+ updated_config_expected_location = os.path.join(
+ mpathconfupdate.MODIFICATIONS_STORE_PATH,
+ config.pathname.lstrip('/')
+ )
+ assert updated_config_location == updated_config_expected_location
+
+ expected_contents = multipathutil.read_config_orig(expected_conf_location)
+ assert actual_contents == expected_contents
+ else:
+ # Unmodified secondary relocated - source is original path
+ assert updated_config_location == config.pathname
+
+
+def test_file_relocation(monkeypatch):
+ """Check that non-default file locations produce UpdatedMultipathConfig entries."""
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked())
+
+ produce_mock = produce_mocked()
+ monkeypatch.setattr(api, 'produce', produce_mock)
+
+ monkeypatch.setattr(multipathutil, 'read_config_orig', multipathutil.read_config, raising=False)
+ monkeypatch.setattr(multipathutil, 'read_config', mock_read_config)
+ monkeypatch.setattr(multipathutil, 'write_config', lambda loc, contents: None)
+ monkeypatch.setattr(mpathconfupdate, 'prepare_destination_for_file', lambda file_path: None)
+ monkeypatch.setattr(mpathconfupdate, 'prepare_place_for_config_modifications', lambda: None)
+
+ facts = build_facts([has_files_conf])
+ mpathconfupdate.update_configs(facts)
+
+ file_updates = {}
+ for config_updates_msg in produce_mock.model_instances:
+ for update in config_updates_msg.updates:
+ file_updates[update.target_path] = update.updated_config_location
+
+ assert file_updates['/etc/multipath/bindings'] == '/tmp/bindings'
+ assert file_updates['/etc/multipath/wwids'] == '/tmp/wwids'
+ assert file_updates['/etc/multipath/prkeys'] == '/tmp/prkeys'
+
+
+def test_default_file_locations_no_relocation(monkeypatch):
+ """Check that default file locations don't produce relocation entries."""
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked())
+
+ produce_mock = produce_mocked()
+ monkeypatch.setattr(api, 'produce', produce_mock)
+
+ conf = build_config(
+ 'has_files.conf',
+ bindings_file='/etc/multipath/bindings',
+ wwids_file='/etc/multipath/wwids',
+ prkeys_file='/etc/multipath/prkeys',
+ )
+
+ monkeypatch.setattr(multipathutil, 'read_config_orig', multipathutil.read_config, raising=False)
+ monkeypatch.setattr(multipathutil, 'read_config', mock_read_config)
+ monkeypatch.setattr(multipathutil, 'write_config', lambda loc, contents: None)
+ monkeypatch.setattr(mpathconfupdate, 'prepare_destination_for_file', lambda file_path: None)
+ monkeypatch.setattr(mpathconfupdate, 'prepare_place_for_config_modifications', lambda: None)
+
+ facts = build_facts([conf])
+ mpathconfupdate.update_configs(facts)
+
+ file_targets = set()
+ for config_updates_msg in produce_mock.model_instances:
+ for update in config_updates_msg.updates:
+ file_targets.add(update.target_path)
+
+ # Config itself is modified (has deprecated options in before file), but no file relocations
+ assert '/etc/multipath/bindings' not in file_targets
+ assert '/etc/multipath/wwids' not in file_targets
+ assert '/etc/multipath/prkeys' not in file_targets
+
+
+def test_last_value_wins_for_files(monkeypatch):
+ """Check that the last non-None value wins for file locations."""
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked())
+
+ produce_mock = produce_mocked()
+ monkeypatch.setattr(api, 'produce', produce_mock)
+
+ primary = build_config(
+ 'no_deprecated.conf', bindings_file='/first/bindings')
+ secondary = build_config(
+ 'secondary_simple.conf', bindings_file='/second/bindings')
+
+ monkeypatch.setattr(multipathutil, 'read_config_orig', multipathutil.read_config, raising=False)
+ monkeypatch.setattr(multipathutil, 'read_config', mock_read_config)
+ monkeypatch.setattr(multipathutil, 'write_config', lambda loc, contents: None)
+ monkeypatch.setattr(mpathconfupdate, 'prepare_destination_for_file', lambda file_path: None)
+ monkeypatch.setattr(mpathconfupdate, 'prepare_place_for_config_modifications', lambda: None)
+
+ facts = build_facts([primary, secondary])
+ mpathconfupdate.update_configs(facts)
+
+ file_updates = {}
+ for config_updates_msg in produce_mock.model_instances:
+ for update in config_updates_msg.updates:
+ file_updates[update.target_path] = update.updated_config_location
+
+ # Last value (/second/bindings) should win
+ assert file_updates['/etc/multipath/bindings'] == '/second/bindings'
+
+
+def test_proposed_config_updates_store(monkeypatch):
+ """Check whether configs are being stored in the expected path."""
+ config = MultipathConfig9to10(
+ pathname='/etc/multipath.conf.d/xy.conf',
+ config_dir='',
+ )
+
+ produce_mock = produce_mocked()
+ monkeypatch.setattr(api, 'produce', produce_mock)
+
+ monkeypatch.setattr(multipathutil, 'write_config', lambda loc, contents: None)
+ monkeypatch.setattr(mpathconfupdate, '_update_config', lambda *args: 'new config content')
+ monkeypatch.setattr(mpathconfupdate, 'prepare_destination_for_file', lambda file_path: None)
+ monkeypatch.setattr(mpathconfupdate, 'prepare_place_for_config_modifications', lambda: None)
+
+ mpathconfupdate.update_configs(MultipathConfFacts9to10(configs=[config]))
+
+ expected_updated_config_path = os.path.join(
+ mpathconfupdate.MODIFICATIONS_STORE_PATH,
+ 'etc/multipath.conf.d/xy.conf'
+ )
+ found = False
+ for config_updates_msg in produce_mock.model_instances:
+ for update in config_updates_msg.updates:
+ if update.updated_config_location == expected_updated_config_path:
+ found = True
+ assert found
--
2.54.0