diff --git a/SOURCES/leapp-repository-0.23.0-elevate.patch b/SOURCES/leapp-repository-0.23.0-elevate.patch index 1bc906e..ef48235 100644 --- a/SOURCES/leapp-repository-0.23.0-elevate.patch +++ b/SOURCES/leapp-repository-0.23.0-elevate.patch @@ -23,6 +23,156 @@ index 0bb92d3d..a04c7ded 100644 # pycharm .idea +diff --git a/.packit.yaml b/.packit.yaml +index 0c3f682a..e158c7e4 100644 +--- a/.packit.yaml ++++ b/.packit.yaml +@@ -110,7 +110,7 @@ jobs: + job: tests + trigger: ignore + fmf_url: "https://gitlab.cee.redhat.com/oamg/leapp-tests" +- fmf_ref: "main" ++ fmf_ref: "next" + use_internal_tf: True + labels: + - sanity +@@ -447,7 +447,7 @@ jobs: + job: tests + trigger: ignore + fmf_url: "https://gitlab.cee.redhat.com/oamg/leapp-tests" +- fmf_ref: "main" ++ fmf_ref: "next" + use_internal_tf: True + labels: + - sanity +@@ -460,6 +460,15 @@ jobs: + tmt: + plan_filter: 'tag:9to10' + environments: ++ - &tmt-env-settings-centos9to10 ++ tmt: ++ context: &tmt-context-centos9to10 ++ distro: "centos-9" ++ distro_target: "centos-10" ++ settings: ++ provisioning: ++ tags: ++ BusinessUnit: sst_upgrades@leapp_upstream_test + - &tmt-env-settings-96to100 + tmt: + context: &tmt-context-96to100 +@@ -478,6 +487,15 @@ jobs: + provisioning: + tags: + BusinessUnit: sst_upgrades@leapp_upstream_test ++ - &tmt-env-settings-centos9torhel101 ++ tmt: ++ context: &tmt-context-centos9torhel101 ++ distro: "centos-9" ++ distro_target: "rhel-10.1" ++ settings: ++ provisioning: ++ tags: ++ BusinessUnit: sst_upgrades@leapp_upstream_test + - &tmt-env-settings-98to102 + tmt: + context: &tmt-context-98to102 +@@ -487,6 +505,15 @@ jobs: + provisioning: + tags: + BusinessUnit: sst_upgrades@leapp_upstream_test ++ - &tmt-env-settings-centos9torhel102 ++ tmt: ++ context: &tmt-context-centos9torhel102 ++ distro: "centos-9" ++ distro_target: "rhel-10.2" ++ settings: ++ provisioning: ++ tags: ++ BusinessUnit: sst_upgrades@leapp_upstream_test + + - &sanity-abstract-9to10-aws + <<: *sanity-abstract-9to10 +@@ -705,3 +732,79 @@ jobs: + env: + <<: *env-98to102 + ++# ###################################################################### # ++# ########################## CentOS Stream ############################# # ++# ###################################################################### # ++ ++# ###################################################################### # ++# ###################### CentOS Stream > RHEL ########################## # ++# ###################################################################### # ++ ++# ###################################################################### # ++# ############################ 9 > 10.1 ################################ # ++# ###################################################################### # ++ ++- &sanity-centos9torhel101 ++ <<: *sanity-abstract-9to10 ++ trigger: pull_request ++ identifier: sanity-CentOS9toRHEL10.1 ++ targets: ++ epel-9-x86_64: ++ distros: [CentOS-Stream-9] ++ tf_extra_params: ++ test: ++ tmt: ++ plan_filter: 'tag:9to10 & tag:tier0 & enabled:true & tag:-rhsm' ++ environments: ++ - *tmt-env-settings-centos9torhel101 ++ env: &env-centos9to101 ++ SOURCE_RELEASE: "9" ++ TARGET_RELEASE: "10.1" ++ ++# ###################################################################### # ++# ############################ 9 > 10.2 ################################ # ++# ###################################################################### # ++ ++- &sanity-centos9torhel102 ++ <<: *sanity-abstract-9to10 ++ trigger: pull_request ++ identifier: sanity-CentOS9toRHEL10.2 ++ targets: ++ epel-9-x86_64: ++ distros: [CentOS-Stream-9] ++ tf_extra_params: ++ test: ++ tmt: ++ plan_filter: 'tag:9to10 & tag:tier0 & enabled:true & tag:-rhsm' ++ name: ++ environments: ++ - *tmt-env-settings-centos9torhel102 ++ env: &env-centos9torhel102 ++ SOURCE_RELEASE: "9" ++ TARGET_RELEASE: "10.2" ++ ++# ###################################################################### # ++# ################## CentOS Stream > CentOS Stream ##################### # ++# ###################################################################### # ++ ++# ###################################################################### # ++# ############################## 9 > 10 ################################ # ++# ###################################################################### # ++ ++- &sanity-centos-9to10 ++ <<: *sanity-abstract-9to10 ++ trigger: pull_request ++ identifier: sanity-CentOS9to10 ++ targets: ++ epel-9-x86_64: ++ distros: [CentOS-Stream-9] ++ tf_extra_params: ++ test: ++ tmt: ++ plan_filter: 'tag:9to10 & tag:tier0 & enabled:true & tag:-rhsm' ++ environments: ++ - *tmt-env-settings-centos9to10 ++ env: &env-centos9to10 ++ SOURCE_RELEASE: "9" ++ TARGET_RELEASE: "10" ++ TARGET_OS: "centos" diff --git a/ci/.gitignore b/ci/.gitignore new file mode 100644 index 00000000..e6f97f0f @@ -3434,6 +3584,18 @@ index 00000000..370758e6 + end + end +end +diff --git a/docs/source/conf.py b/docs/source/conf.py +index a0e6a1de..dd39d3fa 100644 +--- a/docs/source/conf.py ++++ b/docs/source/conf.py +@@ -40,7 +40,6 @@ exclude_patterns = [] + + html_static_path = ['_static'] + html_theme = 'sphinx_rtd_theme' +-html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + + pygments_style = 'sphinx' + diff --git a/docs/source/contrib-and-devel-guidelines.md b/docs/source/contributing/coding-guidelines.md similarity index 68% rename from docs/source/contrib-and-devel-guidelines.md @@ -3638,6 +3800,179 @@ index 27537ca4..ed68f751 100644 faq .. Indices and tables +diff --git a/docs/source/tutorials/howto-single-actor-run.md b/docs/source/tutorials/howto-single-actor-run.md +new file mode 100644 +index 00000000..728ca083 +--- /dev/null ++++ b/docs/source/tutorials/howto-single-actor-run.md +@@ -0,0 +1,155 @@ ++# Running a single Actor ++ ++During development or debugging of actors there may appear a need of running single actor instead of the entire workflow. The advantages of such approach include: ++- **Time and resource efficiency** - Running the entire workflow takes time and resources. Source system is scanned, information is collected and stored, in-place upgrade process goes through several phases. All these actions take time, actors are run multiple times during debugging or development process, so preparing single actor execution lets us save time. ++- **Isolation of problem** - When debugged issue is related to single actor, this approach allows to isolate the issue without interference from other actors. ++ ++ ++```{hint} ++In practice, running a single actor for debugging does not have to be the best way to start when you do not have much experience with Leapp and IPU yet. However, in some cases it's still very valuable and helpful. ++``` ++ ++The execution of an actor using the `snactor` tool seems simple. In case of system upgrade leapp repositories it's not so straightforward and ++it can be quite complicated. In this guide we share our experience how to use `snactor` correctly, describing typical problems that developers hit. ++ ++There are two main approaches: ++- **Running an actor with an empty or non-existent leapp database** -- applicable when a crafted data (or no data at all) is needed. Usually during development. ++- **Running an actor with leapp database filled by previous leapp execution** -- useful for debugging when the leapp.db file is available and want to run the actor in the same context as it has been previously executed when an error occurred. ++ ++```{note} ++The leapp database refers to the `leapp.db` file. In case of using snactor, it's by default present in the `.leapp` directory of the used leapp repository ++scope. ++``` ++ ++````{tip} ++Cleaning the database can be managed with `snactor` tool command: ++```shell ++snactor messages clear ++``` ++In other way, the database file can be also simply removed instead of using snactor. ++```` ++ ++ ++Since an actor seems to be an independent piece of code, there is a dependency chain to resolve inside a workflow, especially around consumed messages and configuration which have to be resolved. When running entire In-Place Upgrade process, those dependencies needed for each actor are satisfied by assignment of each actor to specific phase, where actors emit and consume messages in desired sequence. Single actor usually needs specific list of such requirements, which can be fulfilled by manual preparation of this dependency chain. This very limited amount of resources needed for single actor can be called minimal context. ++ ++ ++## Running a single actor with minimal context ++ ++It is possible to run a single actor without proceeding with `leapp preupgrade` machinery. ++This solution is based on the snactor tool. However, this solution requires minimal context to run. ++ ++As mentioned before and described in [article](https://leapp.readthedocs.io/en/stable/building-blocks-and-architecture.html#architecture-overview) ++about workflow architecture, most of the actors are part of the produce/consume chain of messages. Important step in this procedure is to recreate the sequence of actors to be run to fulfill a chain of dependencies and provide necessary variables. ++ ++Let's explain these steps based on a real case. The following example will be based on the `scan_fips` actor. ++ ++ ++### Initial configuration ++ ++All actors (even those which are not depending on any message emitted by other actors) depend on some initial configuration which is provided by the `ipu_workflow_config` [actor](https://github.com/oamg/leapp-repository/blob/main/repos/system_upgrade/common/actors/ipuworkflowconfig/libraries/ipuworkflowconfig.py). No matter what actor you would like to run, the first step is always to run the `ipu_workflow_config` actor. ++ ++Due to some missing initial variables, which usually are set by the framework, those variables need to be exported manually. Note that following vars are example ones, adjust them to your needs depending on your system configuration: ++```shell ++ ++export LEAPP_UPGRADE_PATH_FLAVOUR=default ++export LEAPP_UPGRADE_PATH_TARGET_RELEASE=9.8 ++export LEAPP_TARGET_OS=9.8 ++``` ++ ++The `ipu_workflow_config` actor produces `IPUWorkflow` message, which contains all required initial config, so at the beginning execute: ++ ++```shell ++snactor run ipu_workflow_config --print-output --save-output ++``` ++ ++```{note} ++Option `--save-output` is necessary to preserve output of this command in Leapp database. Without saving the message, it will not be available for other actors. Option *--print-output* is optional. ++``` ++ ++### Resolving actor's message dependencies ++ ++All basic information what actor consumes and produce can be found in each `actor.py` [code](https://github.com/oamg/leapp-repository/blob/main/repos/system_upgrade/common/actors/scanfips/actor.py#L13-L14). In case of `scan_fips` actor it's: ++ ++```shell ++ consumes = (KernelCmdline,) ++ produces = (FIPSInfo,) ++``` ++ ++This actor consumes one message and produces another. Now we need to track the consumed message, which is `KernelCmdline`. Grep the cloned repository to find that the actor which produces such [message](https://github.com/oamg/leapp-repository/blob/main/repos/system_upgrade/common/actors/scankernelcmdline/actor.py#L14) is `scan_kernel_cmdline`. ++ ++```shell ++snactor run scan_kernel_cmdline --print-output --save-output --actor-config IPUConfig ++``` ++ ++```{note} ++Important step here is to point out what actor config needs to be used, `IPUConfig` in that case. ++This parameter needs to be specified every time you want to run an actor, pointing to proper configuration. ++``` ++ ++This [scan_kernel_cmdline](https://github.com/oamg/leapp-repository/blob/main/repos/system_upgrade/common/actors/scankernelcmdline/actor.py#L13) doesn't consume anything: `consumes = ()`. So finally the desired actor can be run: ++ ++```shell ++snactor run scan_fips --print-output --save-output --actor-config IPUConfig ++``` ++ ++### Limitations ++Note that not all cases will be as simple as the presented one, sometimes actors depend on multiple messages originating from other actors, requiring longer session of environment recreation. ++ ++Also actors designed to run on other architectures will not be able to run. ++ ++## Run single actor with existing database ++ ++In contrast to the previous paragraph, where we operated only on self-created minimal context, the tutorial below will explain how to work with existing or provided context. ++Sometimes - especially for debugging and reproduction of the bug it is very convenient to use provided Leapp database *leapp.db*. This is a file containing all information needed to run Leapp framework on a system, including messages and configurations. Usually all necessary environment for actors is set up by ++first run of `leapp preupgrade` command, when starting from scratch. In this case, we already have `leapp.db` (e.g. transferred from other system) database file. ++ ++Every new run of `leapp` command creates another entry in the database. It creates ++another row in execution table with specific ID, so each context can be easily tracked and ++reproduced. ++ ++See the list of executions using the [leapp-inspector](https://leapp-repository.readthedocs.io/latest/tutorials/troubleshooting-debugging.html#troubleshooting-with-leapp-inspector) tool. ++ ++```shell ++leapp-inspector --db path/to/leapp.db executions ++``` ++Example output: ++```shell ++################################################################## ++ Executions of Leapp ++################################################################## ++Execution | Timestamp ++------------------------------------ | --------------------------- ++d146e105-fafd-43a2-a791-54e141eeab9c | 2025-11-26T19:39:20.563594Z ++b7fd5dca-a49f-4af7-b70c-8bbcc28a4338 | 2025-11-26T19:39:38.034070Z ++50b5289f-be4d-4206-a6e0-73e3caa1f9ed | 2025-11-26T19:41:40.401273Z ++ ++``` ++ ++ ++To determine which context (execution) `leapp` will run, there are two variables: `LEAPP_DEBUG_PRESERVE_CONTEXT` ++and `LEAPP_EXECUTION_ID`. When the `LEAPP_DEBUG_PRESERVE_CONTEXT` is set to 1 and the environment has ++`LEAPP_EXECUTION_ID` set, the `LEAPP_EXECUTION_ID` is not overwritten with snactor's execution ID. ++This allows the developer to run actors in the same way as if the actor was run during the last leapp's ++execution, thus, avoiding to rerun the entire upgrade process. ++ ++ ++Set variables: ++```shell ++ ++export LEAPP_DEBUG_PRESERVE_CONTEXT=1 ++export LEAPP_EXECUTION_ID=50b5289f-be4d-4206-a6e0-73e3caa1f9ed ++``` ++ ++Run desired actors or the entire upgrade process safely now. Output will not be preserved as another context entry. ++```shell ++ ++snactor run --config /etc/leapp/leapp.conf --actor-config IPUConfig --print-output ++``` ++ ++```{note} ++Point to `leapp.conf` file with *--config* option. By default this file is located in `/etc/leapp/` and, among others, it contains Leapp database (`leapp.db`) location. When working with given database, either adjust configuration file or place database file in default location. ++``` ++ ++### Limitations ++ ++Even though the context was provided, it is not possible to run actors which are designed for different architecture than source system. +diff --git a/docs/source/tutorials/index.rst b/docs/source/tutorials/index.rst +index a04fc183..6059e76a 100644 +--- a/docs/source/tutorials/index.rst ++++ b/docs/source/tutorials/index.rst +@@ -19,6 +19,7 @@ write leapp actors for **In-Place Upgrades (IPU)** with the leapp framework. + + setup-devel-env + howto-first-actor-upgrade ++ howto-single-actor-run + custom-content + configurable-actors + templates/index diff --git a/etc/leapp/transaction/to_reinstall b/etc/leapp/transaction/to_reinstall new file mode 100644 index 00000000..c6694a8e @@ -3732,6 +4067,227 @@ index 00000000..52f5af9d + api.produce(ActiveVendorList(data=list(active_vendors))) + else: + self.log.info("No active vendors found, vendor list not generated") +diff --git a/repos/system_upgrade/common/actors/checklvm/actor.py b/repos/system_upgrade/common/actors/checklvm/actor.py +new file mode 100644 +index 00000000..167698db +--- /dev/null ++++ b/repos/system_upgrade/common/actors/checklvm/actor.py +@@ -0,0 +1,24 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor.checklvm import check_lvm ++from leapp.models import DistributionSignedRPM, LVMConfig, TargetUserSpaceUpgradeTasks, UpgradeInitramfsTasks ++from leapp.reporting import Report ++from leapp.tags import ChecksPhaseTag, IPUWorkflowTag ++ ++ ++class CheckLVM(Actor): ++ """ ++ Check if the LVM is installed and ensure the target userspace container ++ and initramfs are prepared to support it. ++ ++ The LVM configuration files are copied into the target userspace container ++ so that the dracut is able to use them while creating the initramfs. ++ The dracut LVM module is enabled by this actor as well. ++ """ ++ ++ name = 'check_lvm' ++ consumes = (DistributionSignedRPM, LVMConfig) ++ produces = (Report, TargetUserSpaceUpgradeTasks, UpgradeInitramfsTasks) ++ tags = (ChecksPhaseTag, IPUWorkflowTag) ++ ++ def process(self): ++ check_lvm() +diff --git a/repos/system_upgrade/common/actors/checklvm/libraries/checklvm.py b/repos/system_upgrade/common/actors/checklvm/libraries/checklvm.py +new file mode 100644 +index 00000000..073bfbf4 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/checklvm/libraries/checklvm.py +@@ -0,0 +1,74 @@ ++import os ++ ++from leapp import reporting ++from leapp.libraries.common.rpms import has_package ++from leapp.libraries.stdlib import api ++from leapp.models import ( ++ CopyFile, ++ DistributionSignedRPM, ++ DracutModule, ++ LVMConfig, ++ TargetUserSpaceUpgradeTasks, ++ UpgradeInitramfsTasks ++) ++ ++LVM_CONFIG_PATH = '/etc/lvm/lvm.conf' ++LVM_DEVICES_FILE_PATH_PREFIX = '/etc/lvm/devices' ++ ++ ++def _report_filter_detection(): ++ title = 'LVM filter definition detected.' ++ summary = ( ++ 'Beginning with RHEL 9, LVM devices file is used by default to select devices used by ' ++ f'LVM. Since leapp detected the use of LVM filter in the {LVM_CONFIG_PATH} configuration ' ++ 'file, the configuration won\'t be modified to use devices file during the upgrade and ' ++ 'the LVM filter will remain in use after the upgrade.' ++ ) ++ ++ remediation_hint = ( ++ 'While not required, switching to the LVM devices file from the LVM filter is possible ' ++ 'using the following command. The command uses the existing LVM filter to create the system.devices ' ++ 'file which is then used instead of the LVM filter. Before running the command, ' ++ f'make sure that \'use_devicesfile=1\' is set in {LVM_CONFIG_PATH}.' ++ ) ++ remediation_command = ['vgimportdevices'] ++ ++ reporting.create_report([ ++ reporting.Title(title), ++ reporting.Summary(summary), ++ reporting.Remediation(hint=remediation_hint, commands=[remediation_command]), ++ reporting.ExternalLink( ++ title='Limiting LVM device visibility and usage', ++ url='https://red.ht/limiting-lvm-devices-visibility-and-usage', ++ ), ++ reporting.Severity(reporting.Severity.INFO), ++ ]) ++ ++ ++def check_lvm(): ++ if not has_package(DistributionSignedRPM, 'lvm2'): ++ return ++ ++ lvm_config = next(api.consume(LVMConfig), None) ++ if not lvm_config: ++ return ++ ++ lvm_devices_file_path = os.path.join(LVM_DEVICES_FILE_PATH_PREFIX, lvm_config.devices.devicesfile) ++ lvm_devices_file_exists = os.path.isfile(lvm_devices_file_path) ++ ++ filters_used = not lvm_config.devices.use_devicesfile or not lvm_devices_file_exists ++ if filters_used: ++ _report_filter_detection() ++ ++ api.current_logger().debug('Including lvm dracut module.') ++ api.produce(UpgradeInitramfsTasks(include_dracut_modules=[DracutModule(name='lvm')])) ++ ++ copy_files = [] ++ api.current_logger().debug('Copying "{}" to the target userspace.'.format(LVM_CONFIG_PATH)) ++ copy_files.append(CopyFile(src=LVM_CONFIG_PATH)) ++ ++ if lvm_devices_file_exists and lvm_config.devices.use_devicesfile: ++ api.current_logger().debug('Copying "{}" to the target userspace.'.format(lvm_devices_file_path)) ++ copy_files.append(CopyFile(src=lvm_devices_file_path)) ++ ++ api.produce(TargetUserSpaceUpgradeTasks(copy_files=copy_files)) +diff --git a/repos/system_upgrade/common/actors/checklvm/tests/test_checklvm.py b/repos/system_upgrade/common/actors/checklvm/tests/test_checklvm.py +new file mode 100644 +index 00000000..a7da8050 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/checklvm/tests/test_checklvm.py +@@ -0,0 +1,92 @@ ++import os ++ ++import pytest ++ ++from leapp.libraries.actor import checklvm ++from leapp.libraries.common.testutils import produce_mocked ++from leapp.libraries.stdlib import api ++from leapp.models import ( ++ DistributionSignedRPM, ++ LVMConfig, ++ LVMConfigDevicesSection, ++ RPM, ++ TargetUserSpaceUpgradeTasks, ++ UpgradeInitramfsTasks ++) ++ ++ ++def test_check_lvm_when_lvm_not_installed(monkeypatch): ++ def consume_mocked(model): ++ if model == LVMConfig: ++ assert False ++ if model == DistributionSignedRPM: ++ yield DistributionSignedRPM(items=[]) ++ ++ monkeypatch.setattr(api, 'produce', produce_mocked()) ++ monkeypatch.setattr(api, 'consume', consume_mocked) ++ ++ checklvm.check_lvm() ++ ++ assert not api.produce.called ++ ++ ++@pytest.mark.parametrize( ++ ('config', 'create_report', 'devices_file_exists'), ++ [ ++ (LVMConfig(devices=LVMConfigDevicesSection(use_devicesfile=False)), True, False), ++ (LVMConfig(devices=LVMConfigDevicesSection(use_devicesfile=True)), False, True), ++ (LVMConfig(devices=LVMConfigDevicesSection(use_devicesfile=True)), True, False), ++ (LVMConfig(devices=LVMConfigDevicesSection(use_devicesfile=False, devicesfile="test.devices")), True, False), ++ (LVMConfig(devices=LVMConfigDevicesSection(use_devicesfile=True, devicesfile="test.devices")), False, True), ++ (LVMConfig(devices=LVMConfigDevicesSection(use_devicesfile=True, devicesfile="test.devices")), True, False), ++ ] ++) ++def test_scan_when_lvm_installed(monkeypatch, config, create_report, devices_file_exists): ++ lvm_package = RPM( ++ name='lvm2', ++ version='2', ++ release='1', ++ epoch='1', ++ packager='', ++ arch='x86_64', ++ pgpsig='RSA/SHA256, Mon 01 Jan 1970 00:00:00 AM -03, Key ID 199e2f91fd431d51' ++ ) ++ ++ def isfile_mocked(_): ++ return devices_file_exists ++ ++ def consume_mocked(model): ++ if model == LVMConfig: ++ yield config ++ if model == DistributionSignedRPM: ++ yield DistributionSignedRPM(items=[lvm_package]) ++ ++ def report_filter_detection_mocked(): ++ assert create_report ++ ++ monkeypatch.setattr(api, 'produce', produce_mocked()) ++ monkeypatch.setattr(api, 'consume', consume_mocked) ++ monkeypatch.setattr(os.path, 'isfile', isfile_mocked) ++ monkeypatch.setattr(checklvm, '_report_filter_detection', report_filter_detection_mocked) ++ ++ checklvm.check_lvm() ++ ++ # The lvm is installed, thus the dracut module is enabled and at least the lvm.conf is copied ++ assert api.produce.called == 2 ++ assert len(api.produce.model_instances) == 2 ++ ++ expected_copied_files = [checklvm.LVM_CONFIG_PATH] ++ if devices_file_exists and config.devices.use_devicesfile: ++ devices_file_path = os.path.join(checklvm.LVM_DEVICES_FILE_PATH_PREFIX, config.devices.devicesfile) ++ expected_copied_files.append(devices_file_path) ++ ++ for produced_model in api.produce.model_instances: ++ assert isinstance(produced_model, (UpgradeInitramfsTasks, TargetUserSpaceUpgradeTasks)) ++ ++ if isinstance(produced_model, UpgradeInitramfsTasks): ++ assert len(produced_model.include_dracut_modules) == 1 ++ assert produced_model.include_dracut_modules[0].name == 'lvm' ++ else: ++ assert len(produced_model.copy_files) == len(expected_copied_files) ++ for file in produced_model.copy_files: ++ assert file.src in expected_copied_files +diff --git a/repos/system_upgrade/common/actors/checkrootsymlinks/actor.py b/repos/system_upgrade/common/actors/checkrootsymlinks/actor.py +index c35272b2..7b89bf7a 100644 +--- a/repos/system_upgrade/common/actors/checkrootsymlinks/actor.py ++++ b/repos/system_upgrade/common/actors/checkrootsymlinks/actor.py +@@ -55,7 +55,7 @@ class CheckRootSymlinks(Actor): + os.path.relpath(item.target, '/'), + os.path.join('/', item.name)]) + commands.append(command) +- rem_commands = [['sh', '-c', ' && '.join(commands)]] ++ rem_commands = [['sh', '-c', '"{}"'.format(' && '.join(commands))]] + # Generate reports about non-utf8 absolute links presence + nonutf_count = len(absolute_links_nonutf) + if nonutf_count > 0: diff --git a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/do-upgrade.sh b/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/do-upgrade.sh index 56a94b5d..46c5d9b6 100755 --- a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/do-upgrade.sh @@ -3741,6 +4297,446 @@ index 56a94b5d..46c5d9b6 100755 mount -o "remount,$old_opts" "$NEWROOT" exit $result - +diff --git a/repos/system_upgrade/common/actors/convert/swapdistropackages/actor.py b/repos/system_upgrade/common/actors/convert/swapdistropackages/actor.py +new file mode 100644 +index 00000000..f8d9c446 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/convert/swapdistropackages/actor.py +@@ -0,0 +1,20 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import swapdistropackages ++from leapp.models import DistributionSignedRPM, RpmTransactionTasks ++from leapp.tags import ChecksPhaseTag, IPUWorkflowTag ++ ++ ++class SwapDistroPackages(Actor): ++ """ ++ Swap distribution specific packages. ++ ++ Does nothing if not converting. ++ """ ++ ++ name = 'swap_distro_packages' ++ consumes = (DistributionSignedRPM,) ++ produces = (RpmTransactionTasks,) ++ tags = (IPUWorkflowTag, ChecksPhaseTag) ++ ++ def process(self): ++ swapdistropackages.process() +diff --git a/repos/system_upgrade/common/actors/convert/swapdistropackages/libraries/swapdistropackages.py b/repos/system_upgrade/common/actors/convert/swapdistropackages/libraries/swapdistropackages.py +new file mode 100644 +index 00000000..f7e2ce68 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/convert/swapdistropackages/libraries/swapdistropackages.py +@@ -0,0 +1,111 @@ ++import fnmatch ++ ++from leapp.exceptions import StopActorExecutionError ++from leapp.libraries.common.config import get_source_distro_id, get_target_distro_id ++from leapp.libraries.common.config.version import get_target_major_version ++from leapp.libraries.stdlib import api ++from leapp.models import DistributionSignedRPM, RpmTransactionTasks ++ ++# Config for swapping distribution-specific RPMs ++# The keys can be in 2 "formats": ++# (, ) ++# (, , ) ++# The "swap" dict maps packages on the source distro to their replacements on ++# the target distro ++# The "remove" set lists packages or glob pattern for matching packages from ++# the source distro to remove without any replacement. ++_CONFIG = { ++ ("centos", "rhel"): { ++ "swap": { ++ "centos-logos": "redhat-logos", ++ "centos-logos-httpd": "redhat-logos-httpd", ++ "centos-logos-ipa": "redhat-logos-ipa", ++ "centos-indexhtml": "redhat-indexhtml", ++ "centos-backgrounds": "redhat-backgrounds", ++ "centos-stream-release": "redhat-release", ++ }, ++ "remove": { ++ "centos-gpg-keys", ++ "centos-stream-repos", ++ # various release packages, typically contain repofiles ++ "centos-release-*", ++ # present on Centos (not Stream) 8, let's include them if they are potentially leftover ++ "centos-linux-release", ++ "centos-linux-repos", ++ "centos-obsolete-packages", ++ }, ++ }, ++ ("almalinux", "rhel"): { ++ "swap": { ++ "almalinux-logos": "redhat-logos", ++ "almalinux-logos-httpd": "redhat-logos-httpd", ++ "almalinux-logos-ipa": "redhat-logos-ipa", ++ "almalinux-indexhtml": "redhat-indexhtml", ++ "almalinux-backgrounds": "redhat-backgrounds", ++ "almalinux-release": "redhat-release", ++ }, ++ "remove": { ++ "almalinux-repos", ++ "almalinux-gpg-keys", ++ ++ "almalinux-release-*", ++ "centos-release-*", ++ "elrepo-release", ++ "epel-release", ++ }, ++ }, ++} ++ ++ ++def _get_config(source_distro, target_distro, target_major): ++ key = (source_distro, target_distro, target_major) ++ config = _CONFIG.get(key) ++ if config: ++ return config ++ ++ key = (source_distro, target_distro) ++ return _CONFIG.get(key) ++ ++ ++def _glob_match_rpms(rpms, pattern): ++ return [rpm for rpm in rpms if fnmatch.fnmatch(rpm, pattern)] ++ ++ ++def _make_transaction_tasks(config, rpms): ++ to_install = set() ++ to_remove = set() ++ for source_pkg, target_pkg in config.get("swap", {}).items(): ++ if source_pkg in rpms: ++ to_remove.add(source_pkg) ++ to_install.add(target_pkg) ++ ++ for pkg in config.get("remove", {}): ++ matches = _glob_match_rpms(rpms, pkg) ++ to_remove.update(matches) ++ ++ return RpmTransactionTasks(to_install=list(to_install), to_remove=list(to_remove)) ++ ++ ++def process(): ++ rpms_msg = next(api.consume(DistributionSignedRPM), None) ++ if not rpms_msg: ++ raise StopActorExecutionError("Did not receive DistributionSignedRPM message") ++ ++ source_distro = get_source_distro_id() ++ target_distro = get_target_distro_id() ++ ++ if source_distro == target_distro: ++ return ++ ++ config = _get_config(source_distro, target_distro, get_target_major_version()) ++ if not config: ++ api.current_logger().warning( ++ "Could not find config for handling distro specific packages for {}->{} upgrade.".format( ++ source_distro, target_distro ++ ) ++ ) ++ return ++ ++ rpms = {rpm.name for rpm in rpms_msg.items} ++ task = _make_transaction_tasks(config, rpms) ++ api.produce(task) +diff --git a/repos/system_upgrade/common/actors/convert/swapdistropackages/tests/test_swapdistropackages.py b/repos/system_upgrade/common/actors/convert/swapdistropackages/tests/test_swapdistropackages.py +new file mode 100644 +index 00000000..99bb9c20 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/convert/swapdistropackages/tests/test_swapdistropackages.py +@@ -0,0 +1,291 @@ ++from unittest import mock ++ ++import pytest ++ ++from leapp.exceptions import StopActorExecutionError ++from leapp.libraries.actor import swapdistropackages ++from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked, produce_mocked ++from leapp.libraries.stdlib import api ++from leapp.models import DistributionSignedRPM, RPM, RpmTransactionTasks ++ ++ ++def test_get_config(monkeypatch): ++ test_config = { ++ ("centos", "rhel"): { ++ "swap": {"pkgA": "pkgB"}, ++ "remove": { ++ "pkgC", ++ }, ++ }, ++ ("centos", "rhel", 10): {"swap": {"pkg1": "pkg2"}}, ++ } ++ monkeypatch.setattr(swapdistropackages, "_CONFIG", test_config) ++ ++ expect = { ++ "swap": {"pkgA": "pkgB"}, ++ "remove": { ++ "pkgC", ++ }, ++ } ++ # fallback to (centos, rhel) when there is no target version specific config ++ cfg = swapdistropackages._get_config("centos", "rhel", 9) ++ assert cfg == expect ++ ++ # has it's own target version specific config ++ cfg = swapdistropackages._get_config("centos", "rhel", 10) ++ assert cfg == {"swap": {"pkg1": "pkg2"}} ++ ++ # not mapped ++ cfg = swapdistropackages._get_config("almalinux", "rhel", 9) ++ assert not cfg ++ ++ ++@pytest.mark.parametrize( ++ "rpms,config,expected", ++ [ ++ ( ++ ["pkgA", "pkgB", "pkgC"], ++ { ++ "swap": {"pkgA": "pkgB"}, ++ "remove": { ++ "pkgC", ++ }, ++ }, ++ RpmTransactionTasks(to_install=["pkgB"], to_remove=["pkgA", "pkgC"]), ++ ), ++ # only some pkgs present ++ ( ++ ["pkg1", "pkgA", "pkg-other"], ++ { ++ "swap": {"pkgX": "pkgB", "pkg1": "pkg2"}, ++ "remove": {"pkg*"}, ++ }, ++ RpmTransactionTasks( ++ to_install=["pkg2"], to_remove=["pkgA", "pkg1", "pkg-other"] ++ ), ++ ), ++ ( ++ ["pkgA", "pkgB"], ++ {}, ++ RpmTransactionTasks(to_install=[], to_remove=[]), ++ ), ++ ], ++) ++def test__make_transaction_tasks(rpms, config, expected): ++ tasks = swapdistropackages._make_transaction_tasks(config, rpms) ++ assert set(tasks.to_install) == set(expected.to_install) ++ assert set(tasks.to_remove) == set(expected.to_remove) ++ ++ ++def test_process_ok(monkeypatch): ++ def _msg_pkgs(pkgnames): ++ rpms = [] ++ for name in pkgnames: ++ rpms.append(RPM( ++ name=name, ++ epoch="0", ++ packager="packager", ++ version="1.2", ++ release="el9", ++ arch="noarch", ++ pgpsig="", ++ )) ++ return DistributionSignedRPM(items=rpms) ++ ++ rpms = [ ++ "centos-logos", ++ "centos-logos-httpd", ++ "centos-logos-ipa", ++ "centos-indexhtml", ++ "centos-backgrounds", ++ "centos-stream-release", ++ "centos-gpg-keys", ++ "centos-stream-repos", ++ "centos-linux-release", ++ "centos-linux-repos", ++ "centos-obsolete-packages", ++ "centos-release-automotive", ++ "centos-release-automotive-experimental", ++ "centos-release-autosd", ++ "centos-release-ceph-pacific", ++ "centos-release-ceph-quincy", ++ "centos-release-ceph-reef", ++ "centos-release-ceph-squid", ++ "centos-release-ceph-tentacle", ++ "centos-release-cloud", ++ "centos-release-gluster10", ++ "centos-release-gluster11", ++ "centos-release-gluster9", ++ "centos-release-hyperscale", ++ "centos-release-hyperscale-experimental", ++ "centos-release-hyperscale-experimental-testing", ++ "centos-release-hyperscale-spin", ++ "centos-release-hyperscale-spin-testing", ++ "centos-release-hyperscale-testing", ++ "centos-release-isa-override", ++ "centos-release-kmods", ++ "centos-release-kmods-kernel", ++ "centos-release-kmods-kernel-6", ++ "centos-release-messaging", ++ "centos-release-nfs-ganesha4", ++ "centos-release-nfs-ganesha5", ++ "centos-release-nfs-ganesha6", ++ "centos-release-nfs-ganesha7", ++ "centos-release-nfs-ganesha8", ++ "centos-release-nfv-common", ++ "centos-release-nfv-openvswitch", ++ "centos-release-okd-4", ++ "centos-release-openstack-antelope", ++ "centos-release-openstack-bobcat", ++ "centos-release-openstack-caracal", ++ "centos-release-openstack-dalmatian", ++ "centos-release-openstack-epoxy", ++ "centos-release-openstack-yoga", ++ "centos-release-openstack-zed", ++ "centos-release-openstackclient-xena", ++ "centos-release-opstools", ++ "centos-release-ovirt45", ++ "centos-release-ovirt45-testing", ++ "centos-release-proposed_updates", ++ "centos-release-rabbitmq-38", ++ "centos-release-samba414", ++ "centos-release-samba415", ++ "centos-release-samba416", ++ "centos-release-samba417", ++ "centos-release-samba418", ++ "centos-release-samba419", ++ "centos-release-samba420", ++ "centos-release-samba421", ++ "centos-release-samba422", ++ "centos-release-samba423", ++ "centos-release-storage-common", ++ "centos-release-virt-common", ++ ] ++ curr_actor_mocked = CurrentActorMocked( ++ src_distro="centos", ++ dst_distro="rhel", ++ msgs=[_msg_pkgs(rpms)], ++ ) ++ monkeypatch.setattr(api, 'current_actor', curr_actor_mocked) ++ produce_mock = produce_mocked() ++ monkeypatch.setattr(api, 'produce', produce_mock) ++ ++ swapdistropackages.process() ++ ++ expected = RpmTransactionTasks( ++ to_install=[ ++ "redhat-logos", ++ "redhat-logos-httpd", ++ "redhat-logos-ipa", ++ "redhat-indexhtml", ++ "redhat-backgrounds", ++ "redhat-release", ++ ], ++ to_remove=rpms, ++ ) ++ ++ assert produce_mock.called == 1 ++ produced = produce_mock.model_instances[0] ++ assert set(produced.to_install) == set(expected.to_install) ++ assert set(produced.to_remove) == set(expected.to_remove) ++ ++ ++def test_process_no_config_skip(monkeypatch): ++ curr_actor_mocked = CurrentActorMocked( ++ src_distro="distroA", dst_distro="distroB", msgs=[DistributionSignedRPM()] ++ ) ++ monkeypatch.setattr(api, "current_actor", curr_actor_mocked) ++ monkeypatch.setattr(swapdistropackages, "_get_config", lambda *args: None) ++ monkeypatch.setattr(api, "current_logger", logger_mocked()) ++ produce_mock = produce_mocked() ++ monkeypatch.setattr(api, "produce", produce_mock) ++ ++ swapdistropackages.process() ++ ++ assert produce_mock.called == 0 ++ assert ( ++ "Could not find config for handling distro specific packages for distroA->distroB upgrade" ++ ) in api.current_logger.warnmsg[0] ++ ++ ++@pytest.mark.parametrize("distro", ["rhel", "centos"]) ++def test_process_not_converting_skip(monkeypatch, distro): ++ curr_actor_mocked = CurrentActorMocked( ++ src_distro=distro, dst_distro=distro, msgs=[DistributionSignedRPM()] ++ ) ++ monkeypatch.setattr(api, "current_actor", curr_actor_mocked) ++ monkeypatch.setattr(api, "current_logger", logger_mocked()) ++ produce_mock = produce_mocked() ++ monkeypatch.setattr(api, "produce", produce_mock) ++ ++ with mock.patch( ++ "leapp.libraries.actor.swapdistropackages._get_config" ++ ) as _get_config_mocked: ++ swapdistropackages.process() ++ _get_config_mocked.assert_not_called() ++ assert produce_mock.called == 0 ++ ++ ++def test_process_no_rpms_mgs(monkeypatch): ++ curr_actor_mocked = CurrentActorMocked(src_distro='centos', dst_distro='rhel') ++ monkeypatch.setattr(api, "current_actor", curr_actor_mocked) ++ produce_mock = produce_mocked() ++ monkeypatch.setattr(api, "produce", produce_mock) ++ ++ with pytest.raises( ++ StopActorExecutionError, ++ match="Did not receive DistributionSignedRPM message" ++ ): ++ swapdistropackages.process() ++ ++ assert produce_mock.called == 0 ++ ++ ++@pytest.mark.parametrize( ++ "pattern, expect", ++ [ ++ ( ++ "centos-release-*", ++ [ ++ "centos-release-samba420", ++ "centos-release-okd-4", ++ "centos-release-opstools", ++ ], ++ ), ++ ( ++ "almalinux-release-*", ++ [ ++ "almalinux-release-testing", ++ "almalinux-release-devel", ++ ], ++ ), ++ ( ++ "epel-release", ++ ["epel-release"], ++ ), ++ ], ++) ++def test_glob_match_rpms(pattern, expect): ++ """ ++ A simple test making sure the fnmatch works correctly for RPM names ++ since it was originally meant for filepaths. ++ """ ++ ++ TEST_GLOB_RPMS = [ ++ "centos-release-samba420", ++ "centos-stream-repos", ++ "centos-release-okd-4", ++ "centos-release", ++ "centos-release-opstools", ++ "release-centos", ++ "almalinux-release-devel", ++ "almalinux-release", ++ "almalinux-repos", ++ "release-almalinux", ++ "vim", ++ "epel-release", ++ "almalinux-release-testing", ++ "gcc-devel" ++ ] ++ actual = swapdistropackages._glob_match_rpms(TEST_GLOB_RPMS, pattern) ++ assert set(actual) == set(expect) diff --git a/repos/system_upgrade/common/actors/distributionsignedrpmscanner/actor.py b/repos/system_upgrade/common/actors/distributionsignedrpmscanner/actor.py index 003f3fc5..9e7bbf4a 100644 --- a/repos/system_upgrade/common/actors/distributionsignedrpmscanner/actor.py @@ -3945,6 +4941,104 @@ index 582a5821..18f2c33f 100644 + to_reinstall=list(to_reinstall), modules_to_reset=list(modules_to_reset.values()), modules_to_enable=list(modules_to_enable.values()))) +diff --git a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/files/bundled_units/boot.mount b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/files/bundled_units/boot.mount +index 869c5e4c..531f6c75 100644 +--- a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/files/bundled_units/boot.mount ++++ b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/files/bundled_units/boot.mount +@@ -1,8 +1,8 @@ + [Unit] + DefaultDependencies=no + Before=local-fs.target +-After=sysroot-boot.target +-Requires=sysroot-boot.target ++After=sysroot-boot.mount ++Requires=sysroot-boot.mount + + [Mount] + What=/sysroot/boot +diff --git a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/actor.py b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/actor.py +index d99bab48..c0c93036 100644 +--- a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/actor.py ++++ b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/actor.py +@@ -6,6 +6,7 @@ from leapp.models import ( + BootContent, + FIPSInfo, + LiveModeConfig, ++ LVMConfig, + TargetOSInstallationImage, + TargetUserSpaceInfo, + TargetUserSpaceUpgradeTasks, +@@ -31,6 +32,7 @@ class UpgradeInitramfsGenerator(Actor): + consumes = ( + FIPSInfo, + LiveModeConfig, ++ LVMConfig, + RequiredUpgradeInitramPackages, # deprecated + TargetOSInstallationImage, + TargetUserSpaceInfo, +diff --git a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py +index f7e4a8af..03447b7c 100644 +--- a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py ++++ b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py +@@ -12,6 +12,7 @@ from leapp.models import UpgradeDracutModule # deprecated + from leapp.models import ( + BootContent, + LiveModeConfig, ++ LVMConfig, + TargetOSInstallationImage, + TargetUserSpaceInfo, + TargetUserSpaceUpgradeTasks, +@@ -193,6 +194,7 @@ def _copy_files(context, files): + context.remove_tree(file_task.dst) + context.copytree_to(file_task.src, file_task.dst) + else: ++ context.makedirs(os.path.dirname(file_task.dst)) + context.copy_to(file_task.src, file_task.dst) + + +@@ -363,20 +365,29 @@ def generate_initram_disk(context): + def fmt_module_list(module_list): + return ','.join(mod.name for mod in module_list) + ++ env_variables = [ ++ 'LEAPP_KERNEL_VERSION={kernel_version}', ++ 'LEAPP_ADD_DRACUT_MODULES="{dracut_modules}"', ++ 'LEAPP_KERNEL_ARCH={arch}', ++ 'LEAPP_ADD_KERNEL_MODULES="{kernel_modules}"', ++ 'LEAPP_DRACUT_INSTALL_FILES="{files}"' ++ ] ++ ++ if next(api.consume(LVMConfig), None): ++ env_variables.append('LEAPP_DRACUT_LVMCONF="1"') ++ ++ env_variables = ' '.join(env_variables) ++ env_variables = env_variables.format( ++ kernel_version=_get_target_kernel_version(context), ++ dracut_modules=fmt_module_list(initramfs_includes.dracut_modules), ++ kernel_modules=fmt_module_list(initramfs_includes.kernel_modules), ++ arch=api.current_actor().configuration.architecture, ++ files=' '.join(initramfs_includes.files) ++ ) ++ cmd = os.path.join('/', INITRAM_GEN_SCRIPT_NAME) ++ + # FIXME: issue #376 +- context.call([ +- '/bin/sh', '-c', +- 'LEAPP_KERNEL_VERSION={kernel_version} ' +- 'LEAPP_ADD_DRACUT_MODULES="{dracut_modules}" LEAPP_KERNEL_ARCH={arch} ' +- 'LEAPP_ADD_KERNEL_MODULES="{kernel_modules}" ' +- 'LEAPP_DRACUT_INSTALL_FILES="{files}" {cmd}'.format( +- kernel_version=_get_target_kernel_version(context), +- dracut_modules=fmt_module_list(initramfs_includes.dracut_modules), +- kernel_modules=fmt_module_list(initramfs_includes.kernel_modules), +- arch=api.current_actor().configuration.architecture, +- files=' '.join(initramfs_includes.files), +- cmd=os.path.join('/', INITRAM_GEN_SCRIPT_NAME)) +- ], env=env) ++ context.call(['/bin/sh', '-c', f'{env_variables} {cmd}'], env=env) + + boot_files_info = copy_boot_files(context) + return boot_files_info diff --git a/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/libraries/missinggpgkey.py b/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/libraries/missinggpgkey.py index 32e4527b..1e595e9a 100644 --- a/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/libraries/missinggpgkey.py @@ -3990,6 +5084,743 @@ index 32e4527b..1e595e9a 100644 @suppress_deprecation(TMPTargetRepositoriesFacts) +diff --git a/repos/system_upgrade/common/actors/multipath/config_reader/actor.py b/repos/system_upgrade/common/actors/multipath/config_reader/actor.py +new file mode 100644 +index 00000000..a7238a25 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/multipath/config_reader/actor.py +@@ -0,0 +1,28 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import multipathconfread ++from leapp.models import DistributionSignedRPM, MultipathConfFacts8to9, MultipathInfo ++from leapp.tags import FactsPhaseTag, IPUWorkflowTag ++ ++ ++class MultipathConfRead(Actor): ++ """ ++ Read multipath configuration files and extract the necessary information ++ ++ Related files: ++ - /etc/multipath.conf ++ - /etc/multipath/ - any files inside the directory ++ - /etc/xdrdevices.conf ++ ++ Two kinds of messages are generated: ++ - MultipathInfo - general information about multipath, version agnostic ++ - upgrade-path-specific messages such as MultipathConfFacts8to9 (produced only ++ when upgrading from 8 to 9) ++ """ ++ ++ name = 'multipath_conf_read' ++ consumes = (DistributionSignedRPM,) ++ produces = (MultipathInfo, MultipathConfFacts8to9) ++ tags = (FactsPhaseTag, IPUWorkflowTag) ++ ++ def process(self): ++ multipathconfread.scan_and_emit_multipath_info() +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/libraries/multipathconfread.py b/repos/system_upgrade/common/actors/multipath/config_reader/libraries/multipathconfread.py +similarity index 54% +rename from repos/system_upgrade/el8toel9/actors/multipathconfread/libraries/multipathconfread.py +rename to repos/system_upgrade/common/actors/multipath/config_reader/libraries/multipathconfread.py +index 5b1cef50..e733500b 100644 +--- a/repos/system_upgrade/el8toel9/actors/multipathconfread/libraries/multipathconfread.py ++++ b/repos/system_upgrade/common/actors/multipath/config_reader/libraries/multipathconfread.py +@@ -2,15 +2,10 @@ import errno + import os + + from leapp.libraries.common import multipathutil ++from leapp.libraries.common.config.version import get_source_major_version + from leapp.libraries.common.rpms import has_package + from leapp.libraries.stdlib import api +-from leapp.models import ( +- CopyFile, +- DistributionSignedRPM, +- MultipathConfFacts8to9, +- MultipathConfig8to9, +- TargetUserSpaceUpgradeTasks +-) ++from leapp.models import DistributionSignedRPM, MultipathConfFacts8to9, MultipathConfig8to9, MultipathInfo + + _regexes = ('vendor', 'product', 'revision', 'product_blacklist', 'devnode', + 'wwid', 'property', 'protocol') +@@ -88,46 +83,30 @@ def is_processable(): + return res + + +-def get_multipath_conf_facts(config_file='/etc/multipath.conf'): +- res_configs = [] +- conf = _parse_config(config_file) +- if not conf: +- return None +- res_configs.append(conf) +- if conf.config_dir: +- res_configs.extend(_parse_config_dir(conf.config_dir)) +- else: +- res_configs.extend(_parse_config_dir('/etc/multipath/conf.d')) +- return MultipathConfFacts8to9(configs=res_configs) +- ++def scan_and_emit_multipath_info(default_config_path='/etc/multipath.conf'): ++ if not is_processable(): ++ return + +-def produce_copy_to_target_task(): +- """ +- Produce task to copy files into the target userspace ++ primary_config = _parse_config(default_config_path) ++ if not primary_config: ++ api.current_logger().debug( ++ 'Primary multipath config /etc/multipath.conf is not present - multipath ' ++ 'is not used.' ++ ) ++ mpath_info = MultipathInfo(is_configured=False) ++ api.produce(mpath_info) ++ return + +- The multipath configuration files are needed when the upgrade init ramdisk +- is generated to ensure we are able to boot into the upgrade environment +- and start the upgrade process itself. By this msg it's told that these +- files/dirs will be available when the upgrade init ramdisk is generated. ++ multipath_info = MultipathInfo( ++ is_configured=True, ++ config_dir=primary_config.config_dir or '/etc/multipath/conf.d' ++ ) ++ api.produce(multipath_info) + +- See TargetUserSpaceUpgradeTasks and UpgradeInitramfsTasks for more info. +- """ +- # TODO(pstodulk): move the function to the multipathconfcheck actor +- # and get rid of the hardcoded stuff. +- # - The current behaviour looks from the user POV same as before this +- # * commit. I am going to keep the proper fix for additional PR as we do +- # * not want to make the current PR even more complex than now and the solution +- # * is not so trivial. +- # - As well, I am missing some information around xDR devices, which are +- # * possibly not handled correctly (maybe missing some executables?..) +- # * Update: practically we do not have enough info about xDR drivers, but +- # * discussed with Ben Marzinski, as the multipath dracut module includes +- # * the xDR utils stuff, we should handle it in the same way. +- # * See xdrgetuid, xdrgetinfo (these two utils are now missing in our initramfs) +- copy_files = [] +- for fname in ['/etc/multipath.conf', '/etc/multipath', '/etc/xdrdevices.conf']: +- if os.path.exists(fname): +- copy_files.append(CopyFile(src=fname)) ++ # Handle upgrade-path-specific config actions ++ if get_source_major_version() == '8': ++ secondary_configs = _parse_config_dir(multipath_info.config_dir) ++ all_configs = [primary_config] + secondary_configs + +- if copy_files: +- api.produce(TargetUserSpaceUpgradeTasks(copy_files=copy_files)) ++ config_facts_for_8to9 = MultipathConfFacts8to9(configs=all_configs) ++ api.produce(config_facts_for_8to9) +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/all_the_things.conf b/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/all_the_things.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/all_the_things.conf +rename to repos/system_upgrade/common/actors/multipath/config_reader/tests/files/all_the_things.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/allow_usb.conf b/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/allow_usb.conf +similarity index 99% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/allow_usb.conf +rename to repos/system_upgrade/common/actors/multipath/config_reader/tests/files/allow_usb.conf +index 57b6f97b..39681b85 100644 +--- a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/allow_usb.conf ++++ b/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/allow_usb.conf +@@ -1074,5 +1074,5 @@ multipaths { + multipath { + wwid "33333333000001388" + alias "foo" +- } ++ } + } +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/complicated.conf b/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/complicated.conf +similarity index 99% +rename from repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/complicated.conf +rename to repos/system_upgrade/common/actors/multipath/config_reader/tests/files/complicated.conf +index 23d93ecf..c889461c 100644 +--- a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/complicated.conf ++++ b/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/complicated.conf +@@ -1103,5 +1103,5 @@ multipaths { + multipath { + wwid "33333333000001388" + alias "foo" +- } ++ } + } +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/conf1.d/empty.conf b/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/conf1.d/empty.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/conf1.d/empty.conf +rename to repos/system_upgrade/common/actors/multipath/config_reader/tests/files/conf1.d/empty.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/conf1.d/nothing_important.conf b/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/conf1.d/nothing_important.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/conf1.d/nothing_important.conf +rename to repos/system_upgrade/common/actors/multipath/config_reader/tests/files/conf1.d/nothing_important.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/conf2.d/all_true.conf b/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/conf2.d/all_true.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/conf2.d/all_true.conf +rename to repos/system_upgrade/common/actors/multipath/config_reader/tests/files/conf2.d/all_true.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/conf3.d/README b/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/conf3.d/README +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/conf3.d/README +rename to repos/system_upgrade/common/actors/multipath/config_reader/tests/files/conf3.d/README +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/converted_the_things.conf b/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/converted_the_things.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/converted_the_things.conf +rename to repos/system_upgrade/common/actors/multipath/config_reader/tests/files/converted_the_things.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/default_rhel8.conf b/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/default_rhel8.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/default_rhel8.conf +rename to repos/system_upgrade/common/actors/multipath/config_reader/tests/files/default_rhel8.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/empty.conf b/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/empty.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/empty.conf +rename to repos/system_upgrade/common/actors/multipath/config_reader/tests/files/empty.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/empty_dir.conf b/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/empty_dir.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/empty_dir.conf +rename to repos/system_upgrade/common/actors/multipath/config_reader/tests/files/empty_dir.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/missing_dir.conf b/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/missing_dir.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/missing_dir.conf +rename to repos/system_upgrade/common/actors/multipath/config_reader/tests/files/missing_dir.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/no_defaults.conf b/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/no_defaults.conf +similarity index 99% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/no_defaults.conf +rename to repos/system_upgrade/common/actors/multipath/config_reader/tests/files/no_defaults.conf +index f7885ca8..ec8ddee2 100644 +--- a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/no_defaults.conf ++++ b/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/no_defaults.conf +@@ -1045,5 +1045,5 @@ multipaths { + multipath { + wwid "33333333000001388" + alias "foo" +- } ++ } + } +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/no_foreign.conf b/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/no_foreign.conf +similarity index 99% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/no_foreign.conf +rename to repos/system_upgrade/common/actors/multipath/config_reader/tests/files/no_foreign.conf +index 9525731c..87f9a24c 100644 +--- a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/no_foreign.conf ++++ b/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/no_foreign.conf +@@ -1085,5 +1085,5 @@ multipaths { + multipath { + wwid "33333333000001388" + alias "foo" +- } ++ } + } +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/not_set_dir.conf b/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/not_set_dir.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/not_set_dir.conf +rename to repos/system_upgrade/common/actors/multipath/config_reader/tests/files/not_set_dir.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/set_in_dir.conf b/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/set_in_dir.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/set_in_dir.conf +rename to repos/system_upgrade/common/actors/multipath/config_reader/tests/files/set_in_dir.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/two_defaults.conf b/repos/system_upgrade/common/actors/multipath/config_reader/tests/files/two_defaults.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/two_defaults.conf +rename to repos/system_upgrade/common/actors/multipath/config_reader/tests/files/two_defaults.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/test_multipath_conf_read_8to9.py b/repos/system_upgrade/common/actors/multipath/config_reader/tests/test_multipath_conf_read_8to9.py +similarity index 58% +rename from repos/system_upgrade/el8toel9/actors/multipathconfread/tests/test_multipath_conf_read_8to9.py +rename to repos/system_upgrade/common/actors/multipath/config_reader/tests/test_multipath_conf_read_8to9.py +index 9134e1d7..e593a857 100644 +--- a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/test_multipath_conf_read_8to9.py ++++ b/repos/system_upgrade/common/actors/multipath/config_reader/tests/test_multipath_conf_read_8to9.py +@@ -1,7 +1,11 @@ + import os + ++import pytest ++ + from leapp.libraries.actor import multipathconfread +-from leapp.models import MultipathConfig8to9 ++from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked ++from leapp.libraries.stdlib import api ++from leapp.models import MultipathConfFacts8to9, MultipathConfig8to9, MultipathInfo + + TEST_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'files') + +@@ -100,45 +104,71 @@ def test_parse_config(): + assert_config(config, expected_data) + + +-def test_get_facts_missing_dir(monkeypatch): ++@pytest.mark.parametrize( ++ ('primary_config', 'expected_configs'), ++ [ ++ ('missing_dir.conf', [missing_dir_conf]), ++ ('empty_dir.conf', [empty_dir_conf]), ++ ('not_set_dir.conf', [not_set_dir_conf, empty1_conf, nothing_important_conf]), ++ ('set_in_dir.conf', [set_in_dir_conf, all_true_conf]), ++ ] ++) ++def test_get_facts_missing_dir(monkeypatch, primary_config, expected_configs): + monkeypatch.setattr(multipathconfread, '_parse_config_orig', multipathconfread._parse_config, raising=False) + monkeypatch.setattr(multipathconfread, '_parse_config', mock_parse_config) ++ monkeypatch.setattr(multipathconfread, 'is_processable', lambda: True) + +- facts = multipathconfread.get_multipath_conf_facts(os.path.join(TEST_DIR, 'missing_dir.conf')) +- assert facts +- assert len(facts.configs) == 1 +- assert_config(facts.configs[0], missing_dir_conf) ++ produce_mock = produce_mocked() ++ monkeypatch.setattr(api, 'produce', produce_mock) + ++ actor_mock = CurrentActorMocked(src_ver='8.10', dst_ver='9.6') ++ monkeypatch.setattr(api, 'current_actor', actor_mock) + +-def test_get_facts_empty_dir(monkeypatch): +- monkeypatch.setattr(multipathconfread, '_parse_config_orig', multipathconfread._parse_config, raising=False) +- monkeypatch.setattr(multipathconfread, '_parse_config', mock_parse_config) ++ config_to_use = os.path.join(TEST_DIR, primary_config) ++ multipathconfread.scan_and_emit_multipath_info(config_to_use) + +- facts = multipathconfread.get_multipath_conf_facts(os.path.join(TEST_DIR, 'empty_dir.conf')) +- assert facts +- assert len(facts.configs) == 1 +- assert_config(facts.configs[0], empty_dir_conf) ++ assert produce_mock.called + ++ general_info = [msg for msg in produce_mock.model_instances if isinstance(msg, MultipathInfo)] ++ assert len(general_info) == 1 ++ assert general_info[0].is_configured ++ # general_info[0].config_dir is with the MultipathConfFacts8to9 messages below + +-def test_get_facts_not_set_dir(monkeypatch): +- monkeypatch.setattr(multipathconfread, '_parse_config_orig', multipathconfread._parse_config, raising=False) +- monkeypatch.setattr(multipathconfread, '_parse_config', mock_parse_config) ++ msgs = [msg for msg in produce_mock.model_instances if isinstance(msg, MultipathConfFacts8to9)] ++ assert len(msgs) == 1 + +- expected_configs = (not_set_dir_conf, empty1_conf, nothing_important_conf) +- facts = multipathconfread.get_multipath_conf_facts(os.path.join(TEST_DIR, 'not_set_dir.conf')) +- assert facts +- assert len(facts.configs) == 3 +- for i in range(len(facts.configs)): +- assert_config(facts.configs[i], expected_configs[i]) ++ actual_configs = msgs[0].configs ++ assert len(actual_configs) == len(expected_configs) + ++ for actual_config, expected_config in zip(actual_configs, expected_configs): ++ assert_config(actual_config, expected_config) + +-def test_get_facts_set_in_dir(monkeypatch): +- monkeypatch.setattr(multipathconfread, '_parse_config_orig', multipathconfread._parse_config, raising=False) +- monkeypatch.setattr(multipathconfread, '_parse_config', mock_parse_config) + +- expected_configs = (set_in_dir_conf, all_true_conf) +- facts = multipathconfread.get_multipath_conf_facts(os.path.join(TEST_DIR, 'set_in_dir.conf')) +- assert facts +- assert len(facts.configs) == 2 +- for i in range(len(facts.configs)): +- assert_config(facts.configs[i], expected_configs[i]) ++def test_only_general_info_is_produced_on_9to10(monkeypatch): ++ default_config_path = '/etc/multipath.conf' ++ ++ def parse_config_mock(path): ++ assert path == default_config_path ++ return MultipathConfig8to9(pathname=path) ++ ++ monkeypatch.setattr(multipathconfread, '_parse_config', parse_config_mock) ++ monkeypatch.setattr(multipathconfread, 'is_processable', lambda: True) ++ ++ produce_mock = produce_mocked() ++ monkeypatch.setattr(api, 'produce', produce_mock) ++ ++ actor_mock = CurrentActorMocked(src_ver='9.6', dst_ver='10.0') ++ monkeypatch.setattr(api, 'current_actor', actor_mock) ++ ++ multipathconfread.scan_and_emit_multipath_info(default_config_path) ++ ++ assert produce_mock.called ++ ++ general_info_msgs = [msg for msg in produce_mock.model_instances if isinstance(msg, MultipathInfo)] ++ assert len(general_info_msgs) == 1 ++ general_info = general_info_msgs[0] ++ assert general_info.is_configured ++ assert general_info.config_dir == '/etc/multipath/conf.d' ++ ++ msgs = [msg for msg in produce_mock.model_instances if isinstance(msg, MultipathConfFacts8to9)] ++ assert not msgs +diff --git a/repos/system_upgrade/common/actors/multipath/system_conf_patcher/actor.py b/repos/system_upgrade/common/actors/multipath/system_conf_patcher/actor.py +new file mode 100644 +index 00000000..44d4fd3b +--- /dev/null ++++ b/repos/system_upgrade/common/actors/multipath/system_conf_patcher/actor.py +@@ -0,0 +1,23 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import system_config_patcher ++from leapp.models import MultipathConfigUpdatesInfo ++from leapp.tags import ApplicationsPhaseTag, IPUWorkflowTag ++ ++ ++class MultipathSystemConfigPatcher(Actor): ++ """ ++ Propagate any modified multipath configs to the source system. ++ ++ We copy, modify and use multipath configs from the source system in the upgrade initramfs ++ as the configs might be incompatible with the target system. Once the upgrade is performed, ++ actual system's configs need to be modified in the same fashion. This is achieved by simply ++ copying our modified multipath configs that were used to upgrade the system. ++ """ ++ ++ name = 'multipath_system_config_patcher' ++ consumes = (MultipathConfigUpdatesInfo,) ++ produces = () ++ tags = (ApplicationsPhaseTag, IPUWorkflowTag) ++ ++ def process(self): ++ system_config_patcher.patch_system_configs() +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 +new file mode 100644 +index 00000000..0d873322 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/multipath/system_conf_patcher/libraries/system_config_patcher.py +@@ -0,0 +1,17 @@ ++import shutil ++ ++from leapp.libraries.stdlib import api ++from leapp.models import MultipathConfigUpdatesInfo ++ ++ ++def patch_system_configs(): ++ for config_updates in api.consume(MultipathConfigUpdatesInfo): ++ for modified_config in config_updates.updates: ++ api.current_logger().debug( ++ 'Copying modified multipath config {} to {}.'.format( ++ modified_config.updated_config_location, ++ modified_config.target_path ++ ) ++ ) ++ ++ shutil.copy(modified_config.updated_config_location, modified_config.target_path) +diff --git a/repos/system_upgrade/common/actors/multipath/system_conf_patcher/tests/test_config_patcher.py b/repos/system_upgrade/common/actors/multipath/system_conf_patcher/tests/test_config_patcher.py +new file mode 100644 +index 00000000..1151fb69 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/multipath/system_conf_patcher/tests/test_config_patcher.py +@@ -0,0 +1,41 @@ ++import shutil ++ ++from leapp.libraries.actor import system_config_patcher ++from leapp.libraries.common.testutils import CurrentActorMocked ++from leapp.libraries.stdlib import api ++from leapp.models import MultipathConfigUpdatesInfo, UpdatedMultipathConfig ++ ++ ++def test_config_patcher(monkeypatch): ++ modified_configs = [ ++ UpdatedMultipathConfig( ++ updated_config_location='/var/lib/leapp/planned_conf_modifications/etc/multipath.conf', ++ target_path='/etc/multipath.conf' ++ ), ++ UpdatedMultipathConfig( ++ updated_config_location='/var/lib/leapp/planned_conf_modifications/etc/multipath/conf.d/myconfig.conf', ++ target_path='/etc/multipath/conf.d/myconfig.conf' ++ ) ++ ] ++ config_update_info = MultipathConfigUpdatesInfo(updates=modified_configs) ++ ++ actor_mock = CurrentActorMocked(msgs=[config_update_info]) ++ monkeypatch.setattr(api, 'current_actor', actor_mock) ++ ++ copies_performed = [] ++ ++ def copy_mock(src, dst, *args, **kwargs): ++ copies_performed.append((src, dst)) ++ ++ monkeypatch.setattr(shutil, 'copy', copy_mock) ++ system_config_patcher.patch_system_configs() ++ ++ expected_copies = [ ++ ('/var/lib/leapp/planned_conf_modifications/etc/multipath.conf', '/etc/multipath.conf'), ++ ( ++ '/var/lib/leapp/planned_conf_modifications/etc/multipath/conf.d/myconfig.conf', ++ '/etc/multipath/conf.d/myconfig.conf' ++ ) ++ ] ++ ++ assert sorted(copies_performed) == expected_copies +diff --git a/repos/system_upgrade/common/actors/multipath/target_uspace_configs/actor.py b/repos/system_upgrade/common/actors/multipath/target_uspace_configs/actor.py +new file mode 100644 +index 00000000..bfe0219e +--- /dev/null ++++ b/repos/system_upgrade/common/actors/multipath/target_uspace_configs/actor.py +@@ -0,0 +1,22 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import target_uspace_multipath_configs ++from leapp.models import MultipathConfigUpdatesInfo, MultipathInfo, TargetUserSpaceUpgradeTasks, UpgradeInitramfsTasks ++from leapp.tags import IPUWorkflowTag, TargetTransactionChecksPhaseTag ++ ++ ++class RequestMultipathConfsInTargetUserspace(Actor): ++ """ ++ Aggregates information about multipath configs. ++ ++ Produces uniform information consisting of copy instructions about which ++ multipath configs (original/updated) should be put into the target ++ userspace. ++ """ ++ ++ name = 'request_multipath_conf_in_target_userspace' ++ consumes = (MultipathInfo, MultipathConfigUpdatesInfo) ++ produces = (TargetUserSpaceUpgradeTasks, UpgradeInitramfsTasks) ++ tags = (TargetTransactionChecksPhaseTag, IPUWorkflowTag) ++ ++ def process(self): ++ target_uspace_multipath_configs.process() +diff --git a/repos/system_upgrade/common/actors/multipath/target_uspace_configs/libraries/target_uspace_multipath_configs.py b/repos/system_upgrade/common/actors/multipath/target_uspace_configs/libraries/target_uspace_multipath_configs.py +new file mode 100644 +index 00000000..72afc477 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/multipath/target_uspace_configs/libraries/target_uspace_multipath_configs.py +@@ -0,0 +1,80 @@ ++import os ++ ++from leapp.libraries.stdlib import api ++from leapp.models import ( ++ CopyFile, ++ DracutModule, ++ MultipathConfigUpdatesInfo, ++ MultipathInfo, ++ TargetUserSpaceUpgradeTasks, ++ UpgradeInitramfsTasks ++) ++ ++ ++def request_mpath_dracut_module_for_upgrade_initramfs(): ++ multipath_mod = DracutModule(name='multipath') ++ request = UpgradeInitramfsTasks(include_dracut_modules=[multipath_mod]) ++ api.produce(request) ++ ++ ++def request_mpath_confs(multipath_info): ++ files_to_put_into_uspace = { # source system path -> target uspace destination ++ '/etc/multipath.conf': '/etc/multipath.conf' # default config ++ } ++ ++ if os.path.exists(multipath_info.config_dir): ++ for filename in os.listdir(multipath_info.config_dir): ++ config_path = os.path.join(multipath_info.config_dir, filename) ++ if not config_path.endswith('.conf'): ++ api.current_logger().debug( ++ 'Skipping {} as it does not have .conf extension'.format(config_path) ++ ) ++ continue ++ files_to_put_into_uspace[config_path] = config_path ++ ++ for config_updates in api.consume(MultipathConfigUpdatesInfo): ++ for update in config_updates.updates: ++ # Detect /etc/multipath.conf > /etc/multipath.conf, and replace it with the patched ++ # version PATCHED > /etc/multipath.conf ++ if update.target_path in files_to_put_into_uspace: ++ del files_to_put_into_uspace[update.target_path] ++ ++ files_to_put_into_uspace[update.updated_config_location] = update.target_path ++ ++ # Note: original implementation would copy the /etc/multipath directory, which contains ++ # /etc/multipath/conf.d location for drop-in files. The current logic includes it automatically, ++ # if the user does not override this default location. In case that the default drop-in location ++ # is changed, this new location is used. ++ additional_files = ['/etc/xdrdevices.conf'] ++ for additional_file in additional_files: ++ if os.path.exists(additional_file): ++ files_to_put_into_uspace[additional_file] = additional_file ++ ++ copy_tasks = [] ++ for source_system_path, target_uspace_path in files_to_put_into_uspace.items(): ++ task = CopyFile(src=source_system_path, dst=target_uspace_path) ++ copy_tasks.append(task) ++ ++ tasks = TargetUserSpaceUpgradeTasks(copy_files=copy_tasks) ++ api.produce(tasks) ++ ++ ++def process(): ++ multipath_info = next(api.consume(MultipathInfo), None) ++ ++ if not multipath_info: ++ api.current_logger().debug( ++ 'Received no MultipathInfo message. No config files will ' ++ 'be requested to be placed into target userspace.' ++ ) ++ return ++ ++ if not multipath_info.is_configured: ++ api.current_logger().debug( ++ 'Multipath is not configured. No config files will ' ++ 'be requested to be placed into target userspace.' ++ ) ++ return ++ ++ request_mpath_confs(multipath_info) ++ request_mpath_dracut_module_for_upgrade_initramfs() +diff --git a/repos/system_upgrade/common/actors/multipath/target_uspace_configs/tests/test_target_uspace_configs.py b/repos/system_upgrade/common/actors/multipath/target_uspace_configs/tests/test_target_uspace_configs.py +new file mode 100644 +index 00000000..ffb63322 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/multipath/target_uspace_configs/tests/test_target_uspace_configs.py +@@ -0,0 +1,86 @@ ++import os ++import shutil ++ ++import pytest ++ ++from leapp.libraries.actor import target_uspace_multipath_configs as actor_lib ++from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked ++from leapp.libraries.stdlib import api ++from leapp.models import ( ++ MultipathConfigUpdatesInfo, ++ MultipathInfo, ++ TargetUserSpaceUpgradeTasks, ++ UpdatedMultipathConfig, ++ UpgradeInitramfsTasks ++) ++ ++ ++@pytest.mark.parametrize( ++ ('multipath_info', 'should_produce'), ++ [ ++ (None, False), # No multipath info message ++ (MultipathInfo(is_configured=False), False), # Multipath is not configured ++ (MultipathInfo(is_configured=True, config_dir='/etc/multipath/conf.d'), True) ++ ] ++) ++def test_production_conditions(monkeypatch, multipath_info, should_produce): ++ """ Test whether messages are produced under right conditions. """ ++ produce_mock = produce_mocked() ++ monkeypatch.setattr(api, 'produce', produce_mock) ++ ++ msgs = [multipath_info] if multipath_info else [] ++ if multipath_info and multipath_info.is_configured: ++ update = UpdatedMultipathConfig( ++ updated_config_location='/var/lib/leapp/proposed_changes/etc/multipath/conf.d/config.conf', ++ target_path='/etc/multipath/conf.d/config.conf' ++ ) ++ msgs.append(MultipathConfigUpdatesInfo(updates=[update])) ++ ++ actor_mock = CurrentActorMocked(msgs=msgs) ++ monkeypatch.setattr(api, 'current_actor', actor_mock) ++ ++ def listdir_mock(path): ++ assert path == '/etc/multipath/conf.d' ++ return ['config.conf', 'config-not-to-be-touched.conf'] ++ ++ def exists_mock(path): ++ return path == '/etc/multipath/conf.d' ++ ++ monkeypatch.setattr(os.path, 'exists', exists_mock) ++ monkeypatch.setattr(os, 'listdir', listdir_mock) ++ ++ actor_lib.process() ++ ++ if should_produce: ++ _target_uspace_tasks = [ ++ msg for msg in produce_mock.model_instances if isinstance(msg, TargetUserSpaceUpgradeTasks) ++ ] ++ assert len(_target_uspace_tasks) == 1 ++ ++ target_uspace_tasks = _target_uspace_tasks[0] ++ ++ copies = sorted((copy.src, copy.dst) for copy in target_uspace_tasks.copy_files) ++ expected_copies = [ ++ ( ++ '/etc/multipath.conf', ++ '/etc/multipath.conf' ++ ), ++ ( ++ '/var/lib/leapp/proposed_changes/etc/multipath/conf.d/config.conf', ++ '/etc/multipath/conf.d/config.conf' ++ ), ++ ( ++ '/etc/multipath/conf.d/config-not-to-be-touched.conf', ++ '/etc/multipath/conf.d/config-not-to-be-touched.conf' ++ ) ++ ] ++ assert copies == sorted(expected_copies) ++ ++ _upgrade_initramfs_tasks = [m for m in produce_mock.model_instances if isinstance(m, UpgradeInitramfsTasks)] ++ assert len(_upgrade_initramfs_tasks) == 1 ++ upgrade_initramfs_tasks = _upgrade_initramfs_tasks[0] ++ ++ dracut_modules = [dracut_mod.name for dracut_mod in upgrade_initramfs_tasks.include_dracut_modules] ++ assert dracut_modules == ['multipath'] ++ else: ++ assert not produce_mock.called +diff --git a/repos/system_upgrade/common/actors/opensshconfigscanner/libraries/readopensshconfig.py b/repos/system_upgrade/common/actors/opensshconfigscanner/libraries/readopensshconfig.py +index 50e37092..f467676b 100644 +--- a/repos/system_upgrade/common/actors/opensshconfigscanner/libraries/readopensshconfig.py ++++ b/repos/system_upgrade/common/actors/opensshconfigscanner/libraries/readopensshconfig.py +@@ -7,6 +7,7 @@ from leapp.exceptions import StopActorExecutionError + from leapp.libraries.common.rpms import check_file_modification + from leapp.libraries.stdlib import api + from leapp.models import OpenSshConfig, OpenSshPermitRootLogin ++from leapp.models.fields import ModelViolationError + + CONFIG = '/etc/ssh/sshd_config' + DEPRECATED_DIRECTIVES = ['showpatchlevel'] +@@ -60,12 +61,35 @@ def parse_config(config, base_config=None, current_cfg_depth=0): + # convert deprecated alias + if value == "without-password": + value = "prohibit-password" +- v = OpenSshPermitRootLogin(value=value, in_match=in_match) ++ try: ++ v = OpenSshPermitRootLogin(value=value, in_match=in_match) ++ except ModelViolationError: ++ valid_values = OpenSshPermitRootLogin.value.serialize()['choices'] ++ raise StopActorExecutionError( ++ 'Invalid SSH configuration: Invalid value for PermitRootLogin', ++ details={ ++ 'details': 'Invalid value "{}" for PermitRootLogin in {}. ' ++ 'Arguments for SSH configuration options are case-sensitive. ' ++ 'Valid values are: {}.' ++ .format(value, CONFIG, ', '.join(valid_values)) ++ } ++ ) + ret.permit_root_login.append(v) + + elif el[0].lower() == 'useprivilegeseparation': + # Record only first occurrence, which is effective + if not ret.use_privilege_separation: ++ valid_values = OpenSshConfig.use_privilege_separation.serialize()['choices'] ++ if value not in valid_values: ++ raise StopActorExecutionError( ++ 'Invalid SSH configuration: Invalid value for UsePrivilegeSeparation', ++ details={ ++ 'details': 'Invalid value "{}" for UsePrivilegeSeparation in {}. ' ++ 'Arguments for SSH configuration options are case-sensitive. ' ++ 'Valid values are: {}.' ++ .format(value, CONFIG, ', '.join(valid_values)) ++ } ++ ) + ret.use_privilege_separation = value + + elif el[0].lower() == 'protocol': +diff --git a/repos/system_upgrade/common/actors/opensshconfigscanner/tests/test_readopensshconfig_opensshconfigscanner.py b/repos/system_upgrade/common/actors/opensshconfigscanner/tests/test_readopensshconfig_opensshconfigscanner.py +index 64c16f7f..1a6a1c9f 100644 +--- a/repos/system_upgrade/common/actors/opensshconfigscanner/tests/test_readopensshconfig_opensshconfigscanner.py ++++ b/repos/system_upgrade/common/actors/opensshconfigscanner/tests/test_readopensshconfig_opensshconfigscanner.py +@@ -351,6 +351,19 @@ def test_produce_config(): + assert cfg.subsystem_sftp == 'internal-sftp' + + ++@pytest.mark.parametrize('config_line,option_name,invalid_value', [ ++ ('PermitRootLogin NO', 'PermitRootLogin', 'NO'), ++ ('UsePrivilegeSeparation YES', 'UsePrivilegeSeparation', 'YES'), ++]) ++def test_parse_config_invalid_option_case(config_line, option_name, invalid_value): ++ config = [config_line] ++ ++ with pytest.raises(StopActorExecutionError) as err: ++ parse_config(config) ++ ++ assert str(err.value).startswith('Invalid SSH configuration') ++ ++ + def test_actor_execution(current_actor_context): + current_actor_context.run() + assert current_actor_context.consume(OpenSshConfig) diff --git a/repos/system_upgrade/common/actors/peseventsscanner/actor.py b/repos/system_upgrade/common/actors/peseventsscanner/actor.py index f801f1a1..cb911471 100644 --- a/repos/system_upgrade/common/actors/peseventsscanner/actor.py @@ -4797,6 +6628,270 @@ index 84895f83..62aefaf4 100644 + to_reinstall=to_reinstall_filtered, to_keep=load_tasks_file(os.path.join(base_dir, 'to_keep'), logger), to_remove=load_tasks_file(os.path.join(base_dir, 'to_remove'), logger)) +diff --git a/repos/system_upgrade/common/actors/scanlvmconfig/actor.py b/repos/system_upgrade/common/actors/scanlvmconfig/actor.py +new file mode 100644 +index 00000000..23ed032d +--- /dev/null ++++ b/repos/system_upgrade/common/actors/scanlvmconfig/actor.py +@@ -0,0 +1,18 @@ ++from leapp.actors import Actor ++from leapp.libraries.actor import scanlvmconfig ++from leapp.models import DistributionSignedRPM, LVMConfig ++from leapp.tags import FactsPhaseTag, IPUWorkflowTag ++ ++ ++class ScanLVMConfig(Actor): ++ """ ++ Scan LVM configuration. ++ """ ++ ++ name = 'scan_lvm_config' ++ consumes = (DistributionSignedRPM,) ++ produces = (LVMConfig,) ++ tags = (FactsPhaseTag, IPUWorkflowTag) ++ ++ def process(self): ++ scanlvmconfig.scan() +diff --git a/repos/system_upgrade/common/actors/scanlvmconfig/libraries/scanlvmconfig.py b/repos/system_upgrade/common/actors/scanlvmconfig/libraries/scanlvmconfig.py +new file mode 100644 +index 00000000..37755e7c +--- /dev/null ++++ b/repos/system_upgrade/common/actors/scanlvmconfig/libraries/scanlvmconfig.py +@@ -0,0 +1,52 @@ ++import os ++ ++from leapp.libraries.common.config import version ++from leapp.libraries.common.rpms import has_package ++from leapp.libraries.stdlib import api ++from leapp.models import DistributionSignedRPM, LVMConfig, LVMConfigDevicesSection ++ ++LVM_CONFIG_PATH = '/etc/lvm/lvm.conf' ++ ++ ++def _lvm_config_devices_parser(lvm_config_lines): ++ in_section = False ++ config = {} ++ for line in lvm_config_lines: ++ line = line.split("#", 1)[0].strip() ++ if not line: ++ continue ++ if "devices {" in line: ++ in_section = True ++ continue ++ if in_section and "}" in line: ++ in_section = False ++ if in_section: ++ value = line.split("=", 1) ++ config[value[0].strip()] = value[1].strip().strip('"') ++ return config ++ ++ ++def _read_config_lines(path): ++ with open(path) as lvm_conf_file: ++ return lvm_conf_file.readlines() ++ ++ ++def scan(): ++ if not has_package(DistributionSignedRPM, 'lvm2'): ++ return ++ ++ if not os.path.isfile(LVM_CONFIG_PATH): ++ api.current_logger().debug('The "{}" is not present on the system.'.format(LVM_CONFIG_PATH)) ++ return ++ ++ lvm_config_lines = _read_config_lines(LVM_CONFIG_PATH) ++ devices_section = _lvm_config_devices_parser(lvm_config_lines) ++ ++ lvm_config_devices = LVMConfigDevicesSection(use_devicesfile=int(version.get_source_major_version()) > 8) ++ if 'devicesfile' in devices_section: ++ lvm_config_devices.devicesfile = devices_section['devicesfile'] ++ ++ if 'use_devicesfile' in devices_section and devices_section['use_devicesfile'] in ['0', '1']: ++ lvm_config_devices.use_devicesfile = devices_section['use_devicesfile'] == '1' ++ ++ api.produce(LVMConfig(devices=lvm_config_devices)) +diff --git a/repos/system_upgrade/common/actors/scanlvmconfig/tests/test_scanlvmconfig.py b/repos/system_upgrade/common/actors/scanlvmconfig/tests/test_scanlvmconfig.py +new file mode 100644 +index 00000000..26728fd8 +--- /dev/null ++++ b/repos/system_upgrade/common/actors/scanlvmconfig/tests/test_scanlvmconfig.py +@@ -0,0 +1,176 @@ ++import os ++ ++import pytest ++ ++from leapp.libraries.actor import scanlvmconfig ++from leapp.libraries.common.config import version ++from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked ++from leapp.libraries.stdlib import api ++from leapp.models import DistributionSignedRPM, LVMConfig, LVMConfigDevicesSection, RPM ++ ++ ++@pytest.mark.parametrize( ++ ("config_as_lines", "config_as_dict"), ++ [ ++ ([], {}), ++ ( ++ ['devices {\n', ++ '\t# comment\n' ++ '}\n'], ++ {} ++ ), ++ ( ++ ['global {\n', ++ 'use_lvmetad = 1\n', ++ '}\n'], ++ {} ++ ), ++ ( ++ ['devices {\n', ++ 'filter = [ "r|/dev/cdrom|", "a|.*|" ]\n', ++ 'use_devicesfile=0\n', ++ 'devicesfile="file-name.devices"\n', ++ '}'], ++ {'filter': '[ "r|/dev/cdrom|", "a|.*|" ]', ++ 'use_devicesfile': '0', ++ 'devicesfile': 'file-name.devices'} ++ ), ++ ( ++ ['devices {\n', ++ 'use_devicesfile = 1\n', ++ 'devicesfile = "file-name.devices"\n', ++ ' }\n'], ++ {'use_devicesfile': '1', ++ 'devicesfile': 'file-name.devices'} ++ ), ++ ( ++ ['devices {\n', ++ ' # comment\n', ++ 'use_devicesfile = 1 # comment\n', ++ '#devicesfile = "file-name.devices"\n', ++ ' }\n'], ++ {'use_devicesfile': '1'} ++ ), ++ ( ++ ['config {\n', ++ '# configuration section\n', ++ '\tabort_on_errors = 1\n', ++ '\tprofile_dir = "/etc/lvm/prifile\n', ++ '}\n', ++ 'devices {\n', ++ ' \n', ++ '\tfilter = ["a|.*|"] \n', ++ '\tuse_devicesfile=0\n', ++ '}\n', ++ 'allocation {\n', ++ '\tcling_tag_list = [ "@site1", "@site2" ]\n', ++ '\tcache_settings {\n', ++ '\t}\n', ++ '}\n' ++ ], ++ {'filter': '["a|.*|"]', 'use_devicesfile': '0'} ++ ), ++ ] ++ ++) ++def test_lvm_config_devices_parser(config_as_lines, config_as_dict): ++ lvm_config = scanlvmconfig._lvm_config_devices_parser(config_as_lines) ++ assert lvm_config == config_as_dict ++ ++ ++def test_scan_when_lvm_not_installed(monkeypatch): ++ def isfile_mocked(_): ++ assert False ++ ++ def read_config_lines_mocked(_): ++ assert False ++ ++ msgs = [ ++ DistributionSignedRPM(items=[]) ++ ] ++ ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) ++ monkeypatch.setattr(api, 'produce', produce_mocked()) ++ monkeypatch.setattr(os.path, 'isfile', isfile_mocked) ++ monkeypatch.setattr(scanlvmconfig, '_read_config_lines', read_config_lines_mocked) ++ ++ scanlvmconfig.scan() ++ ++ assert not api.produce.called ++ ++ ++@pytest.mark.parametrize( ++ ('source_major_version', 'devices_section_dict', 'produced_devices_section'), ++ [ ++ ('8', {}, LVMConfigDevicesSection(use_devicesfile=False)), ++ ('9', {}, LVMConfigDevicesSection(use_devicesfile=True)), ++ ('8', { ++ 'use_devicesfile': '0', ++ }, LVMConfigDevicesSection(use_devicesfile=False, ++ devicesfile='system.devices') ++ ), ++ ('9', { ++ 'use_devicesfile': '0', ++ 'devicesfile': 'file-name.devices' ++ }, LVMConfigDevicesSection(use_devicesfile=False, ++ devicesfile='file-name.devices') ++ ), ++ ++ ('8', { ++ 'use_devicesfile': '1', ++ 'devicesfile': 'file-name.devices' ++ }, LVMConfigDevicesSection(use_devicesfile=True, ++ devicesfile='file-name.devices') ++ ), ++ ('9', { ++ 'use_devicesfile': '1', ++ }, LVMConfigDevicesSection(use_devicesfile=True, ++ devicesfile='system.devices') ++ ), ++ ++ ] ++ ++) ++def test_scan_when_lvm_installed(monkeypatch, source_major_version, devices_section_dict, produced_devices_section): ++ ++ def isfile_mocked(file): ++ assert file == scanlvmconfig.LVM_CONFIG_PATH ++ return True ++ ++ def read_config_lines_mocked(file): ++ assert file == scanlvmconfig.LVM_CONFIG_PATH ++ return ["test_line"] ++ ++ def lvm_config_devices_parser_mocked(lines): ++ assert lines == ["test_line"] ++ return devices_section_dict ++ ++ lvm_package = RPM( ++ name='lvm2', ++ version='2', ++ release='1', ++ epoch='1', ++ packager='', ++ arch='x86_64', ++ pgpsig='RSA/SHA256, Mon 01 Jan 1970 00:00:00 AM -03, Key ID 199e2f91fd431d51' ++ ) ++ ++ msgs = [ ++ DistributionSignedRPM(items=[lvm_package]) ++ ] ++ ++ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs)) ++ monkeypatch.setattr(api, 'produce', produce_mocked()) ++ monkeypatch.setattr(version, 'get_source_major_version', lambda: source_major_version) ++ monkeypatch.setattr(os.path, 'isfile', isfile_mocked) ++ monkeypatch.setattr(scanlvmconfig, '_read_config_lines', read_config_lines_mocked) ++ monkeypatch.setattr(scanlvmconfig, '_lvm_config_devices_parser', lvm_config_devices_parser_mocked) ++ ++ scanlvmconfig.scan() ++ ++ assert api.produce.called == 1 ++ assert len(api.produce.model_instances) == 1 ++ ++ produced_model = api.produce.model_instances[0] ++ assert isinstance(produced_model, LVMConfig) ++ assert produced_model.devices == produced_devices_section diff --git a/repos/system_upgrade/common/actors/scanvendorrepofiles/actor.py b/repos/system_upgrade/common/actors/scanvendorrepofiles/actor.py new file mode 100644 index 00000000..a5e481cb @@ -5945,6 +8040,122 @@ index 00000000..de4056fb +class ActiveVendorList(Model): + topic = VendorTopic + data = fields.List(fields.String()) +diff --git a/repos/system_upgrade/common/models/lvmconfig.py b/repos/system_upgrade/common/models/lvmconfig.py +new file mode 100644 +index 00000000..ab5e7815 +--- /dev/null ++++ b/repos/system_upgrade/common/models/lvmconfig.py +@@ -0,0 +1,26 @@ ++from leapp.models import fields, Model ++from leapp.topics import SystemInfoTopic ++ ++ ++class LVMConfigDevicesSection(Model): ++ """The devices section from the LVM configuration.""" ++ topic = SystemInfoTopic ++ ++ use_devicesfile = fields.Boolean() ++ """ ++ Determines whether only the devices in the devices file are used by LVM. Note ++ that the default value changed on the RHEL 9 to True. ++ """ ++ ++ devicesfile = fields.String(default="system.devices") ++ """ ++ Defines the name of the devices file that should be used. The default devices ++ file is located in '/etc/lvm/devices/system.devices'. ++ """ ++ ++ ++class LVMConfig(Model): ++ """LVM configuration split into sections.""" ++ topic = SystemInfoTopic ++ ++ devices = fields.Model(LVMConfigDevicesSection) +diff --git a/repos/system_upgrade/common/models/multipath.py b/repos/system_upgrade/common/models/multipath.py +new file mode 100644 +index 00000000..1d1c53b5 +--- /dev/null ++++ b/repos/system_upgrade/common/models/multipath.py +@@ -0,0 +1,78 @@ ++from leapp.models import fields, Model ++from leapp.topics import SystemInfoTopic ++ ++ ++class MultipathInfo(Model): ++ """ Available information about multpath devices of the source system. """ ++ topic = SystemInfoTopic ++ ++ is_configured = fields.Boolean(default=False) ++ """ ++ True if multipath is configured on the system. ++ ++ Detected based on checking whether /etc/multipath.conf exists. ++ """ ++ ++ config_dir = fields.Nullable(fields.String()) ++ """ Value of config_dir in the defaults section. None if not set. """ ++ ++ ++class UpdatedMultipathConfig(Model): ++ """ Information about multipath config that needed to be modified for the target system. """ ++ topic = SystemInfoTopic ++ ++ updated_config_location = fields.String() ++ """ Location of the updated config that should be propagated to the source system. """ ++ ++ target_path = fields.String() ++ """ Location where should be the updated config placed. """ ++ ++ ++class MultipathConfigUpdatesInfo(Model): ++ """ Aggregate information about multipath configs that were updated. """ ++ topic = SystemInfoTopic ++ ++ updates = fields.List(fields.Model(UpdatedMultipathConfig), default=[]) ++ """ Collection of multipath config updates that must be performed during the upgrade. """ ++ ++ ++class MultipathConfig8to9(Model): ++ """ ++ Model information about multipath configuration file important for the 8>9 upgrade path. ++ ++ Note: This model is in the common repository due to the technical reasons ++ (reusing parser code in a single actor), and it should not be emitted on ++ non-8to9 upgrade paths. In the future, this model will likely be moved into ++ el8toel9 repository. ++ """ ++ topic = SystemInfoTopic ++ ++ pathname = fields.String() ++ """Config file path name""" ++ ++ config_dir = fields.Nullable(fields.String()) ++ """Value of config_dir in the defaults section. None if not set""" ++ ++ enable_foreign_exists = fields.Boolean(default=False) ++ """True if enable_foreign is set in the defaults section""" ++ ++ invalid_regexes_exist = fields.Boolean(default=False) ++ """True if any regular expressions have the value of "*" """ ++ ++ allow_usb_exists = fields.Boolean(default=False) ++ """True if allow_usb_devices is set in the defaults section.""" ++ ++ ++class MultipathConfFacts8to9(Model): ++ """ ++ Model representing information from multipath configuration files important for the 8>9 upgrade path. ++ ++ Note: This model is in the common repository due to the technical reasons ++ (reusing parser code in a single actor), and it should not be emitted on ++ non-8to9 upgrade paths. In the future, this model will likely be moved into ++ el8toel9 repository. ++ """ ++ topic = SystemInfoTopic ++ ++ configs = fields.List(fields.Model(MultipathConfig8to9), default=[]) ++ """List of multipath configuration files""" diff --git a/repos/system_upgrade/common/models/repositoriesmap.py b/repos/system_upgrade/common/models/repositoriesmap.py index 842cd807..fc740606 100644 --- a/repos/system_upgrade/common/models/repositoriesmap.py @@ -6062,6 +8273,667 @@ index c076fe6b..2455a2f6 100644 UPGRADE_BLS_DIR = '/boot/upgrade-loader' CONTAINER_DOWNLOAD_DIR = '/tmp_pkg_download_dir' +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/actor.py b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/actor.py +similarity index 57% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/actor.py +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/actor.py +index 6c3ef41b..ce6a1ebc 100644 +--- a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/actor.py ++++ b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/actor.py +@@ -1,27 +1,26 @@ + from leapp.actors import Actor + from leapp.libraries.actor import multipathconfupdate +-from leapp.models import MultipathConfFacts8to9 +-from leapp.tags import ApplicationsPhaseTag, IPUWorkflowTag ++from leapp.models import MultipathConfFacts8to9, MultipathConfigUpdatesInfo ++from leapp.tags import IPUWorkflowTag, TargetTransactionChecksPhaseTag + + +-class MultipathConfUpdate8to9(Actor): ++class MultipathUpgradeConfUpdate8to9(Actor): + """ +- Modifies multipath configuration files on the target RHEL-9 system so that +- they will run properly. This is done in three ways ++ Modifies multipath configuration files on the target RHEL-9 upgrade userspace so that ++ we can mount multipath devices during the upgrade. This is done in three ways + 1. Adding the allow_usb_devices and enable_foreign options to + /etc/multipath.conf if they are not present, to retain RHEL-8 behavior + 2. Converting any "*" regular expression strings to ".*" + """ + +- name = 'multipath_conf_update_8to9' ++ name = 'multipath_upgrade_conf_update_8to9' + consumes = (MultipathConfFacts8to9,) +- produces = () +- tags = (ApplicationsPhaseTag, IPUWorkflowTag) ++ produces = (MultipathConfigUpdatesInfo,) ++ tags = (TargetTransactionChecksPhaseTag, IPUWorkflowTag) + + def process(self): + facts = next(self.consume(MultipathConfFacts8to9), None) + if facts is None: +- self.log.debug('Skipping execution. No MultipathConfFacts8to9 has ' +- 'been produced') ++ self.log.debug('Skipping execution. No MultipathConfFacts8to9 has been produced') + return + multipathconfupdate.update_configs(facts) +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/libraries/multipathconfupdate.py b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/libraries/multipathconfupdate.py +similarity index 67% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/libraries/multipathconfupdate.py +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/libraries/multipathconfupdate.py +index 9e49d78f..2dfde7b1 100644 +--- a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/libraries/multipathconfupdate.py ++++ b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/libraries/multipathconfupdate.py +@@ -1,4 +1,11 @@ ++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' + + _regexes = ('vendor', 'product', 'revision', 'product_blacklist', 'devnode', + 'wwid', 'property', 'protocol') +@@ -71,10 +78,37 @@ def _update_config(need_foreign, need_allow_usb, config): + return contents + + ++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): + need_foreign = not any(x for x in facts.configs if x.enable_foreign_exists) + need_allow_usb = not any(x for x in facts.configs if x.allow_usb_exists) ++ ++ config_updates = [] ++ prepare_place_for_config_modifications() ++ + for config in facts.configs: ++ original_config_location = config.pathname ++ ++ rootless_path = config.pathname.lstrip('/') ++ path_to_config_copy = os.path.join(MODIFICATIONS_STORE_PATH, rootless_path) ++ api.current_logger().debug( ++ 'Instead of modyfing {}, preparing modified config at {}'.format( ++ config.pathname, ++ path_to_config_copy ++ ) ++ ) ++ updated_config_location = path_to_config_copy ++ + contents = _update_config(need_foreign, need_allow_usb, config) + need_foreign = False + need_allow_usb = False +@@ -83,4 +117,11 @@ def update_configs(facts): + config file. + """ + if contents: +- multipathutil.write_config(config.pathname, contents) ++ prepare_destination_for_file(updated_config_location) ++ multipathutil.write_config(updated_config_location, contents) ++ ++ update = UpdatedMultipathConfig(updated_config_location=updated_config_location, ++ target_path=original_config_location) ++ config_updates.append(update) ++ ++ api.produce(MultipathConfigUpdatesInfo(updates=config_updates)) +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/all_the_things.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/all_the_things.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/all_the_things.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/all_the_things.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/allow_usb.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/allow_usb.conf +similarity index 99% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/allow_usb.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/allow_usb.conf +index e7a9c23e..0d7ad283 100644 +--- a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/allow_usb.conf ++++ b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/allow_usb.conf +@@ -1075,5 +1075,5 @@ multipaths { + multipath { + wwid "33333333000001388" + alias "foo" +- } ++ } + } +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/complicated.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/complicated.conf +similarity index 99% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/complicated.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/complicated.conf +index cbfaf801..31d3b61d 100644 +--- a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/complicated.conf ++++ b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/complicated.conf +@@ -1104,5 +1104,5 @@ multipaths { + multipath { + wwid "33333333000001388" + alias "foo" +- } ++ } + } +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/conf2.d/all_true.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/conf2.d/all_true.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/conf2.d/all_true.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/conf2.d/all_true.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/default_rhel8.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/default_rhel8.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/default_rhel8.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/default_rhel8.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/empty.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/empty.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/empty.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/empty.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/empty_dir.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/empty_dir.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/empty_dir.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/empty_dir.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/missing_dir.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/missing_dir.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/missing_dir.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/missing_dir.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/no_defaults.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/no_defaults.conf +similarity index 99% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/no_defaults.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/no_defaults.conf +index 02d7c1a2..d50d6a71 100644 +--- a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/no_defaults.conf ++++ b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/no_defaults.conf +@@ -1045,7 +1045,7 @@ multipaths { + multipath { + wwid "33333333000001388" + alias "foo" +- } ++ } + } + + defaults { # section added by Leapp +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/no_foreign.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/no_foreign.conf +similarity index 99% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/no_foreign.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/no_foreign.conf +index 9abffc40..d3d29c29 100644 +--- a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/no_foreign.conf ++++ b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/no_foreign.conf +@@ -1086,5 +1086,5 @@ multipaths { + multipath { + wwid "33333333000001388" + alias "foo" +- } ++ } + } +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/not_set_dir.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/not_set_dir.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/not_set_dir.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/not_set_dir.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/two_defaults.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/two_defaults.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/after/two_defaults.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/after/two_defaults.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/all_the_things.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/all_the_things.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/all_the_things.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/all_the_things.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/allow_usb.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/allow_usb.conf +similarity index 99% +rename from repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/allow_usb.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/allow_usb.conf +index 57b6f97b..39681b85 100644 +--- a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/allow_usb.conf ++++ b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/allow_usb.conf +@@ -1074,5 +1074,5 @@ multipaths { + multipath { + wwid "33333333000001388" + alias "foo" +- } ++ } + } +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/complicated.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/complicated.conf +similarity index 99% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/complicated.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/complicated.conf +index 23d93ecf..c889461c 100644 +--- a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/complicated.conf ++++ b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/complicated.conf +@@ -1103,5 +1103,5 @@ multipaths { + multipath { + wwid "33333333000001388" + alias "foo" +- } ++ } + } +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/conf1.d/empty.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/conf1.d/empty.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/conf1.d/empty.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/conf1.d/empty.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/conf1.d/nothing_important.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/conf1.d/nothing_important.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/conf1.d/nothing_important.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/conf1.d/nothing_important.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/conf2.d/all_true.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/conf2.d/all_true.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/conf2.d/all_true.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/conf2.d/all_true.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/conf3.d/README b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/conf3.d/README +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/conf3.d/README +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/conf3.d/README +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/converted_the_things.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/converted_the_things.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/converted_the_things.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/converted_the_things.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/default_rhel8.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/default_rhel8.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/default_rhel8.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/default_rhel8.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/empty.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/empty.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/empty.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/empty.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/empty_dir.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/empty_dir.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/empty_dir.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/empty_dir.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/missing_dir.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/missing_dir.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/missing_dir.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/missing_dir.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/no_defaults.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/no_defaults.conf +similarity index 99% +rename from repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/no_defaults.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/no_defaults.conf +index f7885ca8..ec8ddee2 100644 +--- a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/no_defaults.conf ++++ b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/no_defaults.conf +@@ -1045,5 +1045,5 @@ multipaths { + multipath { + wwid "33333333000001388" + alias "foo" +- } ++ } + } +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/no_foreign.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/no_foreign.conf +similarity index 99% +rename from repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/no_foreign.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/no_foreign.conf +index 9525731c..87f9a24c 100644 +--- a/repos/system_upgrade/el8toel9/actors/multipathconfread/tests/files/no_foreign.conf ++++ b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/no_foreign.conf +@@ -1085,5 +1085,5 @@ multipaths { + multipath { + wwid "33333333000001388" + alias "foo" +- } ++ } + } +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/not_set_dir.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/not_set_dir.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/not_set_dir.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/not_set_dir.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/set_in_dir.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/set_in_dir.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/set_in_dir.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/set_in_dir.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/two_defaults.conf b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/two_defaults.conf +similarity index 100% +rename from repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/files/before/two_defaults.conf +rename to repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/files/before/two_defaults.conf +diff --git a/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/test_multipath_conf_update_8to9.py b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/test_multipath_conf_update_8to9.py +new file mode 100644 +index 00000000..4ca73791 +--- /dev/null ++++ b/repos/system_upgrade/el8toel9/actors/multipath_upgrade_conf_patcher/tests/test_multipath_conf_update_8to9.py +@@ -0,0 +1,179 @@ ++import os ++ ++import pytest ++ ++from leapp.libraries.actor import multipathconfupdate ++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 MultipathConfFacts8to9, MultipathConfig8to9 ++ ++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, enable_foreign_exists, invalid_regexes_exist, allow_usb_exists): ++ return MultipathConfig8to9( ++ pathname=pathname, ++ config_dir=config_dir, ++ enable_foreign_exists=enable_foreign_exists, ++ invalid_regexes_exist=invalid_regexes_exist, ++ allow_usb_exists=allow_usb_exists, ++ ) ++ ++ ++def build_facts(confs): ++ return MultipathConfFacts8to9(configs=confs) ++ ++ ++def mock_read_config(path): ++ """convert to full pathname""" ++ return multipathutil.read_config_orig(os.path.join(BEFORE_DIR, path)) ++ ++ ++default_rhel8_conf = build_config( ++ 'default_rhel8.conf', None, True, False, False) ++ ++all_the_things_conf = build_config( ++ 'all_the_things.conf', None, False, True, False) ++ ++converted_the_things_conf = build_config( ++ 'converted_the_things.conf', None, True, False, True) ++ ++idempotent_conf = build_config( ++ 'converted_the_things.conf', None, False, True, False) ++ ++complicated_conf = build_config( ++ 'complicated.conf', '/etc/multipath/conf.d', True, True, False) ++ ++no_foreign_conf = build_config( ++ 'no_foreign.conf', None, False, True, True) ++ ++allow_usb_conf = build_config( ++ 'allow_usb.conf', None, False, False, True) ++ ++no_defaults_conf = build_config( ++ 'no_defaults.conf', None, False, True, False) ++ ++two_defaults_conf = build_config( ++ 'two_defaults.conf', None, True, False, False) ++ ++empty_conf = build_config( ++ 'empty.conf', None, False, False, False) ++ ++missing_dir_conf = build_config( ++ 'missing_dir.conf', 'missing', False, True, False) ++ ++not_set_dir_conf = build_config( ++ 'not_set_dir.conf', 'conf1.d', False, True, False) ++ ++empty1_conf = build_config( ++ 'conf1.d/empty.conf', None, False, False, False) ++ ++nothing_important_conf = build_config( ++ 'conf1.d/nothing_important.conf', 'this_gets_ignored', False, False, False) ++ ++set_in_dir_conf = build_config( ++ 'set_in_dir.conf', 'conf2.d', False, False, False) ++ ++all_true_conf = build_config( ++ 'conf2.d/all_true.conf', None, True, True, True) ++ ++empty_dir_conf = build_config( ++ 'empty_dir.conf', 'conf3.d', False, False, False) ++ ++ ++@pytest.mark.parametrize( ++ 'config_facts', ++ [ ++ build_facts([default_rhel8_conf]), ++ build_facts([all_the_things_conf]), ++ build_facts([converted_the_things_conf]), ++ build_facts([complicated_conf]), ++ build_facts([no_foreign_conf]), ++ build_facts([allow_usb_conf]), ++ build_facts([no_defaults_conf]), ++ build_facts([two_defaults_conf]), ++ build_facts([empty_conf]), ++ build_facts([missing_dir_conf]), ++ build_facts([empty_dir_conf]), ++ build_facts([not_set_dir_conf, empty1_conf, nothing_important_conf]), ++ build_facts([set_in_dir_conf, all_true_conf]), ++ build_facts([idempotent_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(multipathconfupdate, 'prepare_destination_for_file', lambda file_path: None) ++ monkeypatch.setattr(multipathconfupdate, 'prepare_place_for_config_modifications', lambda: None) ++ ++ multipathconfupdate.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 ++ ++ for config in config_facts.configs: ++ expected_conf_location = os.path.join(AFTER_DIR, config.pathname) ++ ++ if config.pathname not in config_updates: ++ assert not os.path.exists(expected_conf_location) ++ continue ++ ++ updated_config_location = config_updates[config.pathname] ++ actual_contents = config_writes[updated_config_location] ++ ++ updated_config_expected_location = os.path.join( ++ multipathconfupdate.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 ++ ++ ++def test_proposed_config_updates_store(monkeypatch): ++ """ Check whether configs are being stored in the expected path. """ ++ config = MultipathConfig8to9( ++ pathname='/etc/multipath.conf.d/xy.conf', ++ config_dir='', ++ enable_foreign_exists=False, ++ invalid_regexes_exist=False, ++ allow_usb_exists=False, ++ ) ++ ++ 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, 'write_config', write_config_mock) ++ monkeypatch.setattr(multipathconfupdate, '_update_config', lambda *args: 'new config content') ++ monkeypatch.setattr(multipathconfupdate, 'prepare_destination_for_file', lambda file_path: None) ++ monkeypatch.setattr(multipathconfupdate, 'prepare_place_for_config_modifications', lambda: None) ++ ++ multipathconfupdate.update_configs(MultipathConfFacts8to9(configs=[config])) ++ ++ expected_updated_config_path = os.path.join( ++ multipathconfupdate.MODIFICATIONS_STORE_PATH, ++ 'etc/multipath.conf.d/xy.conf' ++ ) ++ assert expected_updated_config_path in config_writes +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfread/actor.py b/repos/system_upgrade/el8toel9/actors/multipathconfread/actor.py +deleted file mode 100644 +index 2b41ae8b..00000000 +--- a/repos/system_upgrade/el8toel9/actors/multipathconfread/actor.py ++++ /dev/null +@@ -1,33 +0,0 @@ +-from leapp.actors import Actor +-from leapp.libraries.actor import multipathconfread +-from leapp.models import DistributionSignedRPM, MultipathConfFacts8to9, TargetUserSpaceUpgradeTasks +-from leapp.tags import FactsPhaseTag, IPUWorkflowTag +- +- +-class MultipathConfRead8to9(Actor): +- """ +- Read multipath configuration files and extract the necessary information +- +- Related files: +- - /etc/multipath.conf +- - /etc/multipath/ - any files inside the directory +- - /etc/xdrdevices.conf +- +- As well, create task (msg) to copy all needed multipath files into +- the target container as the files are needed to create proper initramfs. +- This covers the files mentioned above. +- """ +- +- name = 'multipath_conf_read_8to9' +- consumes = (DistributionSignedRPM,) +- produces = (MultipathConfFacts8to9, TargetUserSpaceUpgradeTasks) +- tags = (FactsPhaseTag, IPUWorkflowTag) +- +- def process(self): +- if multipathconfread.is_processable(): +- res = multipathconfread.get_multipath_conf_facts() +- if res: +- self.produce(res) +- # Create task to copy multipath config files Iff facts +- # are generated +- multipathconfread.produce_copy_to_target_task() +diff --git a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/test_multipath_conf_update_8to9.py b/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/test_multipath_conf_update_8to9.py +deleted file mode 100644 +index c18d6b85..00000000 +--- a/repos/system_upgrade/el8toel9/actors/multipathconfupdate/tests/test_multipath_conf_update_8to9.py ++++ /dev/null +@@ -1,119 +0,0 @@ +-import os +- +-from leapp.libraries.actor import multipathconfupdate +-from leapp.libraries.common import multipathutil +-from leapp.models import MultipathConfFacts8to9, MultipathConfig8to9 +- +-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') +- +-converted_data = {} +- +- +-def build_config(pathname, config_dir, enable_foreign_exists, invalid_regexes_exist, allow_usb_exists): +- return MultipathConfig8to9( +- pathname=pathname, +- config_dir=config_dir, +- enable_foreign_exists=enable_foreign_exists, +- invalid_regexes_exist=invalid_regexes_exist, +- allow_usb_exists=allow_usb_exists, +- ) +- +- +-def build_facts(confs): +- return MultipathConfFacts8to9(configs=confs) +- +- +-def mock_read_config(path): +- """convert to full pathname""" +- return multipathutil.read_config_orig(os.path.join(BEFORE_DIR, path)) +- +- +-def mock_write_config(path, contents): +- converted_data[path] = contents +- +- +-default_rhel8_conf = build_config( +- 'default_rhel8.conf', None, True, False, False) +- +-all_the_things_conf = build_config( +- 'all_the_things.conf', None, False, True, False) +- +-converted_the_things_conf = build_config( +- 'converted_the_things.conf', None, True, False, True) +- +-idempotent_conf = build_config( +- 'converted_the_things.conf', None, False, True, False) +- +-complicated_conf = build_config( +- 'complicated.conf', '/etc/multipath/conf.d', True, True, False) +- +-no_foreign_conf = build_config( +- 'no_foreign.conf', None, False, True, True) +- +-allow_usb_conf = build_config( +- 'allow_usb.conf', None, False, False, True) +- +-no_defaults_conf = build_config( +- 'no_defaults.conf', None, False, True, False) +- +-two_defaults_conf = build_config( +- 'two_defaults.conf', None, True, False, False) +- +-empty_conf = build_config( +- 'empty.conf', None, False, False, False) +- +-missing_dir_conf = build_config( +- 'missing_dir.conf', 'missing', False, True, False) +- +-not_set_dir_conf = build_config( +- 'not_set_dir.conf', 'conf1.d', False, True, False) +- +-empty1_conf = build_config( +- 'conf1.d/empty.conf', None, False, False, False) +- +-nothing_important_conf = build_config( +- 'conf1.d/nothing_important.conf', 'this_gets_ignored', False, False, False) +- +-set_in_dir_conf = build_config( +- 'set_in_dir.conf', 'conf2.d', False, False, False) +- +-all_true_conf = build_config( +- 'conf2.d/all_true.conf', None, True, True, True) +- +-empty_dir_conf = build_config( +- 'empty_dir.conf', 'conf3.d', False, False, False) +- +-facts_list = [build_facts([default_rhel8_conf]), +- build_facts([all_the_things_conf]), +- build_facts([converted_the_things_conf]), +- build_facts([complicated_conf]), +- build_facts([no_foreign_conf]), +- build_facts([allow_usb_conf]), +- build_facts([no_defaults_conf]), +- build_facts([two_defaults_conf]), +- build_facts([empty_conf]), +- build_facts([missing_dir_conf]), +- build_facts([empty_dir_conf]), +- build_facts([not_set_dir_conf, empty1_conf, nothing_important_conf]), +- build_facts([set_in_dir_conf, all_true_conf]), +- build_facts([idempotent_conf])] +- +- +-def _test_facts(facts): +- multipathconfupdate.update_configs(facts) +- for config in facts.configs: +- expected_data = multipathutil.read_config_orig(os.path.join(AFTER_DIR, config.pathname)) +- if config.pathname in converted_data: +- assert converted_data[config.pathname] == expected_data +- else: +- assert expected_data is None +- +- +-def test_all_facts(monkeypatch): +- monkeypatch.setattr(multipathutil, 'read_config_orig', multipathutil.read_config, raising=False) +- monkeypatch.setattr(multipathutil, 'read_config', mock_read_config) +- monkeypatch.setattr(multipathutil, 'write_config', mock_write_config) +- for facts in facts_list: +- _test_facts(facts) +- converted_data.clear() diff --git a/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py b/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py index daa7b2ca..dd604d8b 100644 --- a/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py @@ -6093,6 +8965,42 @@ index daa7b2ca..dd604d8b 100644 def get_workaround_efi_info(): +diff --git a/repos/system_upgrade/el8toel9/models/multipathconffacts.py b/repos/system_upgrade/el8toel9/models/multipathconffacts.py +deleted file mode 100644 +index 91d3ce35..00000000 +--- a/repos/system_upgrade/el8toel9/models/multipathconffacts.py ++++ /dev/null +@@ -1,30 +0,0 @@ +-from leapp.models import fields, Model +-from leapp.topics import SystemInfoTopic +- +- +-class MultipathConfig8to9(Model): +- """Model representing information about a multipath configuration file""" +- topic = SystemInfoTopic +- +- pathname = fields.String() +- """Config file path name""" +- +- config_dir = fields.Nullable(fields.String()) +- """Value of config_dir in the defaults section. None if not set""" +- +- enable_foreign_exists = fields.Boolean(default=False) +- """True if enable_foreign is set in the defaults section""" +- +- invalid_regexes_exist = fields.Boolean(default=False) +- """True if any regular expressions have the value of "*" """ +- +- allow_usb_exists = fields.Boolean(default=False) +- """True if allow_usb_devices is set in the defaults section.""" +- +- +-class MultipathConfFacts8to9(Model): +- """Model representing information from multipath configuration files""" +- topic = SystemInfoTopic +- +- configs = fields.List(fields.Model(MultipathConfig8to9), default=[]) +- """List of multipath configuration files""" diff --git a/repos/system_upgrade/el9toel10/actors/inhibitcgroupsv1/libraries/inhibitcgroupsv1.py b/repos/system_upgrade/el9toel10/actors/inhibitcgroupsv1/libraries/inhibitcgroupsv1.py index 6c891f22..0a38ace3 100644 --- a/repos/system_upgrade/el9toel10/actors/inhibitcgroupsv1/libraries/inhibitcgroupsv1.py diff --git a/SPECS/leapp-repository.spec b/SPECS/leapp-repository.spec index ca397e3..f9d909f 100644 --- a/SPECS/leapp-repository.spec +++ b/SPECS/leapp-repository.spec @@ -53,7 +53,7 @@ py2_byte_compile "%1" "%2"} Epoch: 1 Name: leapp-repository Version: 0.23.0 -Release: 2%{?dist}.elevate.1 +Release: 2%{?dist}.elevate.2 Summary: Repositories for leapp License: ASL 2.0 @@ -485,6 +485,9 @@ fi %changelog +* Tue Dec 16 2025 Yuriy Kohut - 0.23.0-2.elevate.2 +- ELevate vendors support for upstream 0.23.0-2 version (4105452bc89b36359124f5a20d17b73b7512a928) + * Mon Dec 01 2025 Yuriy Kohut - 0.23.0-2.elevate.1 - ELevate vendors support for upstream 0.23.0-2 version (eabab8c496a7d6a76ff1aa0d7e34b0345530e30a)