leapp-repository/SOURCES/0023-feature-add-possibility-to-use-net.naming-scheme.patch
2024-11-25 09:10:29 +00:00

676 lines
30 KiB
Diff

From e1bdf2c02dd193cdd7a2da95e2a3cfa5e6e1e8b3 Mon Sep 17 00:00:00 2001
From: mhecko <mhecko@redhat.com>
Date: Mon, 29 Apr 2024 11:16:46 +0200
Subject: [PATCH 23/40] feature: add possibility to use net.naming-scheme
Leapp writes .link files to prevent interfaces being renamed
after booting to post-upgrade system. This patch adds a less
error-prone approach that uses net.naming-scheme kernel param.
The naming-scheme tells udev what hardware properties to use
when composing a device name. Moreover, possible values of this
parameter are coarse-grained "profiles", that tell udev to
behave as if it did on RHEL8.0.
The functionality is enabled by setting LEAPP_USE_NET_NAMING_SCHEME
environmental variable to 1. If the feature is enabled, the .link
file generation is disabled. A kernel parameter `net.naming-scheme=`
is added to the upgrade boot entry and the post-upgrade entry.
The value of the parameter will be `rhel-<source_major>.0`. Note
that the minor source version is *not used*. Using also source major
version instead of 0 causes the device names to change slightly,
so we use 0. Moreover, an extra RPM named `rhel-net-naming-sysattrs`
is installed to the target system and target userspace container.
The RPM provides definitions of the "profiles" for net.naming-scheme.
The feature is available only for 8>9 and higher. Attempting to
upgrade 7>8 with LEAPP_USE_NET_NAMING_SCHEME=1 will ignore
the value of LEAPP_USE_NET_NAMING_SCHEME.
Add a possibility to use the net.naming-scheme cmdline argument
to make immutable network interface names during the upgrade.
The feature can be used only for 8>9 upgrades and higher.
To enable the feature, use LEAPP_USE_NET_NAMING_SCHEME=1.
Jira-ref: RHEL-23473
---
.../actors/addupgradebootentry/actor.py | 10 +-
.../libraries/addupgradebootentry.py | 78 ++++++++++-----
.../tests/unit_test_addupgradebootentry.py | 47 ++++-----
.../actors/kernelcmdlineconfig/actor.py | 16 +++-
.../libraries/kernelcmdlineconfig.py | 12 ++-
.../libraries/persistentnetnamesconfig.py | 5 +-
.../common/models/kernelcmdlineargs.py | 21 ++++
.../actors/emit_net_naming_scheme/actor.py | 28 ++++++
.../libraries/emit_net_naming.py | 63 ++++++++++++
.../tests/test_emit_net_naming_scheme.py | 95 +++++++++++++++++++
10 files changed, 318 insertions(+), 57 deletions(-)
create mode 100644 repos/system_upgrade/el8toel9/actors/emit_net_naming_scheme/actor.py
create mode 100644 repos/system_upgrade/el8toel9/actors/emit_net_naming_scheme/libraries/emit_net_naming.py
create mode 100644 repos/system_upgrade/el8toel9/actors/emit_net_naming_scheme/tests/test_emit_net_naming_scheme.py
diff --git a/repos/system_upgrade/common/actors/addupgradebootentry/actor.py b/repos/system_upgrade/common/actors/addupgradebootentry/actor.py
index f400ebf8..e4ecf39e 100644
--- a/repos/system_upgrade/common/actors/addupgradebootentry/actor.py
+++ b/repos/system_upgrade/common/actors/addupgradebootentry/actor.py
@@ -8,11 +8,13 @@ from leapp.models import (
FirmwareFacts,
GrubConfigError,
KernelCmdline,
+ LateTargetKernelCmdlineArgTasks,
LiveImagePreparationInfo,
LiveModeArtifacts,
LiveModeConfig,
TargetKernelCmdlineArgTasks,
- TransactionDryRun
+ TransactionDryRun,
+ UpgradeKernelCmdlineArgTasks
)
from leapp.tags import InterimPreparationPhaseTag, IPUWorkflowTag
@@ -33,9 +35,11 @@ class AddUpgradeBootEntry(Actor):
LiveModeArtifacts,
LiveModeConfig,
KernelCmdline,
- TransactionDryRun
+ TransactionDryRun,
+ TargetKernelCmdlineArgTasks,
+ UpgradeKernelCmdlineArgTasks
)
- produces = (TargetKernelCmdlineArgTasks,)
+ produces = (LateTargetKernelCmdlineArgTasks,)
tags = (IPUWorkflowTag, InterimPreparationPhaseTag)
def process(self):
diff --git a/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py b/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py
index 553ffc35..b236e39b 100644
--- a/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py
+++ b/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py
@@ -9,14 +9,16 @@ from leapp.models import (
BootContent,
KernelCmdline,
KernelCmdlineArg,
+ LateTargetKernelCmdlineArgTasks,
LiveImagePreparationInfo,
LiveModeArtifacts,
LiveModeConfig,
- TargetKernelCmdlineArgTasks
+ TargetKernelCmdlineArgTasks,
+ UpgradeKernelCmdlineArgTasks
)
-def collect_boot_args(livemode_enabled):
+def collect_upgrade_kernel_args(livemode_enabled):
args = {
'enforcing': '0',
'rd.plymouth': '0',
@@ -34,7 +36,10 @@ def collect_boot_args(livemode_enabled):
livemode_args = construct_cmdline_args_for_livemode()
args.update(livemode_args)
- return args
+ upgrade_kernel_args = collect_set_of_kernel_args_from_msgs(UpgradeKernelCmdlineArgTasks, 'to_add')
+ args.update(upgrade_kernel_args)
+
+ return set(args.items())
def collect_undesired_args(livemode_enabled):
@@ -43,11 +48,11 @@ def collect_undesired_args(livemode_enabled):
args = dict(zip(('ro', 'rhgb', 'quiet'), itertools.repeat(None)))
args['rd.lvm.lv'] = _get_rdlvm_arg_values()
- return args
+ return set(args.items())
-def format_grubby_args_from_args_dict(args_dict):
- """ Format the given args dictionary in a form required by grubby's --args. """
+def format_grubby_args_from_args_set(args_dict):
+ """ Format the given args set in a form required by grubby's --args. """
def fmt_single_arg(arg_pair):
key, value = arg_pair
@@ -65,7 +70,7 @@ def format_grubby_args_from_args_dict(args_dict):
else:
yield (key, value) # Just a single (key, value) pair
- arg_sequence = itertools.chain(*(flatten_arguments(arg_pair) for arg_pair in args_dict.items()))
+ arg_sequence = itertools.chain(*(flatten_arguments(arg_pair) for arg_pair in args_dict))
# Sorting should be fine as only values can be None, but we cannot have a (key, None) and (key, value) in
# the dictionary at the same time.
@@ -78,7 +83,7 @@ def format_grubby_args_from_args_dict(args_dict):
def figure_out_commands_needed_to_add_entry(kernel_path, initramfs_path, args_to_add, args_to_remove):
boot_entry_modification_commands = []
- args_to_add_str = format_grubby_args_from_args_dict(args_to_add)
+ args_to_add_str = format_grubby_args_from_args_set(args_to_add)
create_entry_cmd = [
'/usr/sbin/grubby',
@@ -93,7 +98,7 @@ def figure_out_commands_needed_to_add_entry(kernel_path, initramfs_path, args_to
# We need to update root= param separately, since we cannot do it during --add-kernel with --copy-default.
# This is likely a bug in grubby.
- root_param_value = args_to_add.get('root', None)
+ root_param_value = dict(args_to_add).get('root', None)
if root_param_value:
enforce_root_param_for_the_entry_cmd = [
'/usr/sbin/grubby',
@@ -103,7 +108,7 @@ def figure_out_commands_needed_to_add_entry(kernel_path, initramfs_path, args_to
boot_entry_modification_commands.append(enforce_root_param_for_the_entry_cmd)
if args_to_remove:
- args_to_remove_str = format_grubby_args_from_args_dict(args_to_remove)
+ args_to_remove_str = format_grubby_args_from_args_set(args_to_remove)
remove_undesired_args_cmd = [
'/usr/sbin/grubby',
'--update-kernel', kernel_path,
@@ -113,18 +118,55 @@ def figure_out_commands_needed_to_add_entry(kernel_path, initramfs_path, args_to
return boot_entry_modification_commands
+def collect_set_of_kernel_args_from_msgs(msg_type, arg_list_field_name):
+ cmdline_modification_msgs = api.consume(msg_type)
+ lists_of_args_to_add = (getattr(msg, arg_list_field_name, []) for msg in cmdline_modification_msgs)
+ args = itertools.chain(*lists_of_args_to_add)
+ return set((arg.key, arg.value) for arg in args)
+
+
+def emit_removal_of_args_meant_only_for_upgrade_kernel(added_upgrade_kernel_args):
+ """
+ Emit message requesting removal of upgrade kernel args that should not be on the target kernel.
+
+ Target kernel args are created by copying the args of the booted (upgrade) kernel. Therefore,
+ we need to explicitly modify the target kernel cmdline, removing what should not have been copied.
+ """
+ target_args_to_add = collect_set_of_kernel_args_from_msgs(TargetKernelCmdlineArgTasks, 'to_add')
+ actual_kernel_args = collect_set_of_kernel_args_from_msgs(KernelCmdline, 'parameters')
+
+ # actual_kernel_args should not be changed during upgrade, unless explicitly removed by
+ # TargetKernelCmdlineArgTasks.to_remove, but that is handled by some other upgrade component. We just want
+ # to make sure we remove what was not on the source system and that we don't overwrite args to be added to target.
+ args_not_present_on_target_kernel = added_upgrade_kernel_args - actual_kernel_args - target_args_to_add
+
+ # We remove only what we've added and what will not be already removed by someone else.
+ args_to_remove = [KernelCmdlineArg(key=arg[0], value=arg[1]) for arg in args_not_present_on_target_kernel]
+
+ if args_to_remove:
+ msg = ('Following upgrade kernel args were added, but they should not be present '
+ 'on target cmdline: `%s`, requesting removal.')
+ api.current_logger().info(msg, args_not_present_on_target_kernel)
+ args_sorted = sorted(args_to_remove, key=lambda arg: arg.key)
+ api.produce(LateTargetKernelCmdlineArgTasks(to_remove=args_sorted))
+
+
def add_boot_entry(configs=None):
kernel_dst_path, initram_dst_path = get_boot_file_paths()
+
_remove_old_upgrade_boot_entry(kernel_dst_path, configs=configs)
livemode_enabled = next(api.consume(LiveImagePreparationInfo), None) is not None
- cmdline_args = collect_boot_args(livemode_enabled)
+ # We have to keep the desired and unwanted args separate and modify cmline in two separate grubby calls. Merging
+ # these sets and trying to execute only a single command would leave the unwanted cmdline args present if they
+ # are present on the original system.
+ added_cmdline_args = collect_upgrade_kernel_args(livemode_enabled)
undesired_cmdline_args = collect_undesired_args(livemode_enabled)
commands_to_run = figure_out_commands_needed_to_add_entry(kernel_dst_path,
initram_dst_path,
- args_to_add=cmdline_args,
+ args_to_add=added_cmdline_args,
args_to_remove=undesired_cmdline_args)
def run_commands_adding_entry(extra_command_suffix=None):
@@ -146,16 +188,8 @@ def add_boot_entry(configs=None):
# See https://bugzilla.redhat.com/show_bug.cgi?id=1764306
run(['/usr/sbin/zipl'])
- if 'debug' in cmdline_args:
- # The kernelopts for target kernel are generated based on the cmdline used in the upgrade initramfs,
- # therefore, if we enabled debug above, and the original system did not have the debug kernelopt, we
- # need to explicitly remove it from the target os boot entry.
- # NOTE(mhecko): This will also unconditionally remove debug kernelopt if the source system used it.
- api.produce(TargetKernelCmdlineArgTasks(to_remove=[KernelCmdlineArg(key='debug')]))
-
- # NOTE(mmatuska): This will remove the option even if the source system had it set.
- # However enforcing=0 shouldn't be set persistently anyway.
- api.produce(TargetKernelCmdlineArgTasks(to_remove=[KernelCmdlineArg(key='enforcing', value='0')]))
+ effective_upgrade_kernel_args = added_cmdline_args - undesired_cmdline_args
+ emit_removal_of_args_meant_only_for_upgrade_kernel(effective_upgrade_kernel_args)
except CalledProcessError as e:
raise StopActorExecutionError(
diff --git a/repos/system_upgrade/common/actors/addupgradebootentry/tests/unit_test_addupgradebootentry.py b/repos/system_upgrade/common/actors/addupgradebootentry/tests/unit_test_addupgradebootentry.py
index c4f5232b..2f58ba9e 100644
--- a/repos/system_upgrade/common/actors/addupgradebootentry/tests/unit_test_addupgradebootentry.py
+++ b/repos/system_upgrade/common/actors/addupgradebootentry/tests/unit_test_addupgradebootentry.py
@@ -12,6 +12,7 @@ from leapp.models import (
BootContent,
KernelCmdline,
KernelCmdlineArg,
+ LateTargetKernelCmdlineArgTasks,
LiveModeArtifacts,
LiveModeConfig,
TargetKernelCmdlineArgTasks
@@ -82,8 +83,10 @@ def test_add_boot_entry(monkeypatch, run_args, arch):
assert addupgradebootentry.run.args[0] == run_args.args_remove
assert addupgradebootentry.run.args[1] == run_args.args_add
assert api.produce.model_instances == [
- TargetKernelCmdlineArgTasks(to_remove=[KernelCmdlineArg(key='debug')]),
- TargetKernelCmdlineArgTasks(to_remove=[KernelCmdlineArg(key='enforcing', value='0')])
+ LateTargetKernelCmdlineArgTasks(to_remove=[KernelCmdlineArg(key='debug'),
+ KernelCmdlineArg(key='enforcing', value='0'),
+ KernelCmdlineArg(key='plymouth.enable', value='0'),
+ KernelCmdlineArg(key='rd.plymouth', value='0')])
]
if run_args.args_zipl:
@@ -103,16 +106,16 @@ def test_debug_kernelopt_removal_task_production(monkeypatch, is_leapp_invoked_w
CurrentActorMocked(envars={'LEAPP_DEBUG': str(int(is_leapp_invoked_with_debug))}))
addupgradebootentry.add_boot_entry()
+ assert len(api.produce.model_instances) == 1
- expected_produced_messages = []
- if is_leapp_invoked_with_debug:
- expected_produced_messages = [TargetKernelCmdlineArgTasks(to_remove=[KernelCmdlineArg(key='debug')])]
-
- expected_produced_messages.append(
- TargetKernelCmdlineArgTasks(to_remove=[KernelCmdlineArg(key='enforcing', value='0')])
- )
+ produced_msg = api.produce.model_instances[0]
+ assert isinstance(produced_msg, LateTargetKernelCmdlineArgTasks)
- assert api.produce.model_instances == expected_produced_messages
+ debug_kernel_cmline_arg = KernelCmdlineArg(key='debug')
+ if is_leapp_invoked_with_debug:
+ assert debug_kernel_cmline_arg in produced_msg.to_remove
+ else:
+ assert debug_kernel_cmline_arg not in produced_msg.to_remove
def test_add_boot_entry_configs(monkeypatch):
@@ -132,8 +135,10 @@ def test_add_boot_entry_configs(monkeypatch):
assert addupgradebootentry.run.args[2] == run_args_add + ['-c', CONFIGS[0]]
assert addupgradebootentry.run.args[3] == run_args_add + ['-c', CONFIGS[1]]
assert api.produce.model_instances == [
- TargetKernelCmdlineArgTasks(to_remove=[KernelCmdlineArg(key='debug')]),
- TargetKernelCmdlineArgTasks(to_remove=[KernelCmdlineArg(key='enforcing', value='0')]),
+ LateTargetKernelCmdlineArgTasks(to_remove=[KernelCmdlineArg(key='debug'),
+ KernelCmdlineArg(key='enforcing', value='0'),
+ KernelCmdlineArg(key='plymouth.enable', value='0'),
+ KernelCmdlineArg(key='rd.plymouth', value='0')])
]
@@ -183,7 +188,7 @@ def test_fix_grub_config_error(monkeypatch, error_type, test_file_name):
(False, False),
)
)
-def test_collect_boot_args(monkeypatch, is_debug_enabled, network_enablement_type):
+def test_collect_upgrade_kernel_args(monkeypatch, is_debug_enabled, network_enablement_type):
env_vars = {'LEAPP_DEBUG': str(int(is_debug_enabled))}
if network_enablement_type:
env_vars['LEAPP_DEVEL_INITRAM_NETWORK'] = network_enablement_type
@@ -192,7 +197,8 @@ def test_collect_boot_args(monkeypatch, is_debug_enabled, network_enablement_typ
monkeypatch.setattr(addupgradebootentry, 'construct_cmdline_args_for_livemode',
lambda *args: {'livemodearg': 'value'})
- args = addupgradebootentry.collect_boot_args(livemode_enabled=True)
+ arg_set = addupgradebootentry.collect_upgrade_kernel_args(livemode_enabled=True)
+ args = dict(arg_set)
assert args['enforcing'] == '0'
assert args['rd.plymouth'] == '0'
@@ -320,16 +326,3 @@ def test_get_device_uuid(monkeypatch):
uuid = addupgradebootentry._get_device_uuid(path)
assert uuid == 'MY_UUID1'
-
-
-@pytest.mark.parametrize(
- ('args', 'expected_result'),
- (
- ([('argA', 'val'), ('argB', 'valB'), ('argC', None), ], 'argA=val argB=valB argC'),
- ([('argA', ('val1', 'val2'))], 'argA=val1 argA=val2')
- )
-)
-def test_format_grubby_args_from_args_dict(args, expected_result):
- actual_result = addupgradebootentry.format_grubby_args_from_args_dict(dict(args))
-
- assert actual_result == expected_result
diff --git a/repos/system_upgrade/common/actors/kernelcmdlineconfig/actor.py b/repos/system_upgrade/common/actors/kernelcmdlineconfig/actor.py
index 3585a14e..6d5f39dd 100644
--- a/repos/system_upgrade/common/actors/kernelcmdlineconfig/actor.py
+++ b/repos/system_upgrade/common/actors/kernelcmdlineconfig/actor.py
@@ -3,7 +3,13 @@ import os
from leapp.actors import Actor
from leapp.exceptions import StopActorExecutionError
from leapp.libraries.actor import kernelcmdlineconfig
-from leapp.models import FirmwareFacts, InstalledTargetKernelInfo, KernelCmdlineArg, TargetKernelCmdlineArgTasks
+from leapp.models import (
+ FirmwareFacts,
+ InstalledTargetKernelInfo,
+ KernelCmdlineArg,
+ LateTargetKernelCmdlineArgTasks,
+ TargetKernelCmdlineArgTasks
+)
from leapp.reporting import Report
from leapp.tags import FinalizationPhaseTag, IPUWorkflowTag
@@ -14,7 +20,13 @@ class KernelCmdlineConfig(Actor):
"""
name = 'kernelcmdlineconfig'
- consumes = (KernelCmdlineArg, InstalledTargetKernelInfo, FirmwareFacts, TargetKernelCmdlineArgTasks)
+ consumes = (
+ KernelCmdlineArg,
+ InstalledTargetKernelInfo,
+ FirmwareFacts,
+ LateTargetKernelCmdlineArgTasks,
+ TargetKernelCmdlineArgTasks
+ )
produces = (Report,)
tags = (FinalizationPhaseTag, IPUWorkflowTag)
diff --git a/repos/system_upgrade/common/actors/kernelcmdlineconfig/libraries/kernelcmdlineconfig.py b/repos/system_upgrade/common/actors/kernelcmdlineconfig/libraries/kernelcmdlineconfig.py
index 19c50f3c..98b8b95b 100644
--- a/repos/system_upgrade/common/actors/kernelcmdlineconfig/libraries/kernelcmdlineconfig.py
+++ b/repos/system_upgrade/common/actors/kernelcmdlineconfig/libraries/kernelcmdlineconfig.py
@@ -1,3 +1,4 @@
+import itertools
import re
from leapp import reporting
@@ -5,7 +6,12 @@ from leapp.exceptions import StopActorExecutionError
from leapp.libraries import stdlib
from leapp.libraries.common.config import architecture, version
from leapp.libraries.stdlib import api
-from leapp.models import InstalledTargetKernelInfo, KernelCmdlineArg, TargetKernelCmdlineArgTasks
+from leapp.models import (
+ InstalledTargetKernelInfo,
+ KernelCmdlineArg,
+ LateTargetKernelCmdlineArgTasks,
+ TargetKernelCmdlineArgTasks
+)
KERNEL_CMDLINE_FILE = "/etc/kernel/cmdline"
@@ -71,7 +77,9 @@ def retrieve_arguments_to_modify():
kernelargs_msgs_to_add = list(api.consume(KernelCmdlineArg))
kernelargs_msgs_to_remove = []
- for target_kernel_arg_task in api.consume(TargetKernelCmdlineArgTasks):
+ modification_msgs = itertools.chain(api.consume(TargetKernelCmdlineArgTasks),
+ api.consume(LateTargetKernelCmdlineArgTasks))
+ for target_kernel_arg_task in modification_msgs:
kernelargs_msgs_to_add.extend(target_kernel_arg_task.to_add)
kernelargs_msgs_to_remove.extend(target_kernel_arg_task.to_remove)
diff --git a/repos/system_upgrade/common/actors/persistentnetnamesconfig/libraries/persistentnetnamesconfig.py b/repos/system_upgrade/common/actors/persistentnetnamesconfig/libraries/persistentnetnamesconfig.py
index dc5196ea..2f12742a 100644
--- a/repos/system_upgrade/common/actors/persistentnetnamesconfig/libraries/persistentnetnamesconfig.py
+++ b/repos/system_upgrade/common/actors/persistentnetnamesconfig/libraries/persistentnetnamesconfig.py
@@ -2,7 +2,7 @@ import errno
import os
import re
-from leapp.libraries.common.config import get_env
+from leapp.libraries.common.config import get_env, version
from leapp.libraries.stdlib import api
from leapp.models import (
InitrdIncludes,
@@ -39,6 +39,9 @@ def generate_link_file(interface):
@suppress_deprecation(InitrdIncludes)
def process():
+ if get_env('LEAPP_USE_NET_NAMING_SCHEMES', '0') == '1' and version.get_target_major_version() != '8':
+ api.current_logger().info('Skipping generation of .link files renaming NICs as LEAPP_USE_NET_NAMING_SCHEMES=1')
+ return
if get_env('LEAPP_NO_NETWORK_RENAMING', '0') == '1':
api.current_logger().info(
diff --git a/repos/system_upgrade/common/models/kernelcmdlineargs.py b/repos/system_upgrade/common/models/kernelcmdlineargs.py
index e3568a0a..fafd2853 100644
--- a/repos/system_upgrade/common/models/kernelcmdlineargs.py
+++ b/repos/system_upgrade/common/models/kernelcmdlineargs.py
@@ -24,6 +24,27 @@ class TargetKernelCmdlineArgTasks(Model):
to_remove = fields.List(fields.Model(KernelCmdlineArg), default=[])
+class LateTargetKernelCmdlineArgTasks(Model):
+ """
+ Desired modifications of the target kernel args produced later in the upgrade process.
+
+ Defined to prevent loops in the actor dependency graph.
+ """
+ topic = SystemInfoTopic
+
+ to_add = fields.List(fields.Model(KernelCmdlineArg), default=[])
+ to_remove = fields.List(fields.Model(KernelCmdlineArg), default=[])
+
+
+class UpgradeKernelCmdlineArgTasks(Model):
+ """
+ Modifications of the upgrade kernel cmdline.
+ """
+ topic = SystemInfoTopic
+
+ to_add = fields.List(fields.Model(KernelCmdlineArg), default=[])
+
+
class KernelCmdline(Model):
"""
Kernel command line parameters the system was booted with
diff --git a/repos/system_upgrade/el8toel9/actors/emit_net_naming_scheme/actor.py b/repos/system_upgrade/el8toel9/actors/emit_net_naming_scheme/actor.py
new file mode 100644
index 00000000..769fe20b
--- /dev/null
+++ b/repos/system_upgrade/el8toel9/actors/emit_net_naming_scheme/actor.py
@@ -0,0 +1,28 @@
+from leapp.actors import Actor
+from leapp.libraries.actor import emit_net_naming as emit_net_naming_lib
+from leapp.models import (
+ KernelCmdline,
+ RpmTransactionTasks,
+ TargetKernelCmdlineArgTasks,
+ TargetUserSpaceUpgradeTasks,
+ UpgradeKernelCmdlineArgTasks
+)
+from leapp.tags import ChecksPhaseTag, IPUWorkflowTag
+
+
+class EmitNetNamingScheme(Actor):
+ """
+ Emit necessary modifications of the upgrade environment and target command line to use net.naming-scheme.
+ """
+ name = 'emit_net_naming_scheme'
+ consumes = (KernelCmdline,)
+ produces = (
+ RpmTransactionTasks,
+ TargetKernelCmdlineArgTasks,
+ TargetUserSpaceUpgradeTasks,
+ UpgradeKernelCmdlineArgTasks,
+ )
+ tags = (ChecksPhaseTag, IPUWorkflowTag)
+
+ def process(self):
+ emit_net_naming_lib.emit_msgs_to_use_net_naming_schemes()
diff --git a/repos/system_upgrade/el8toel9/actors/emit_net_naming_scheme/libraries/emit_net_naming.py b/repos/system_upgrade/el8toel9/actors/emit_net_naming_scheme/libraries/emit_net_naming.py
new file mode 100644
index 00000000..65abdd4d
--- /dev/null
+++ b/repos/system_upgrade/el8toel9/actors/emit_net_naming_scheme/libraries/emit_net_naming.py
@@ -0,0 +1,63 @@
+from leapp.exceptions import StopActorExecutionError
+from leapp.libraries.common.config import get_env, version
+from leapp.libraries.stdlib import api
+from leapp.models import (
+ KernelCmdline,
+ KernelCmdlineArg,
+ RpmTransactionTasks,
+ TargetKernelCmdlineArgTasks,
+ TargetUserSpaceUpgradeTasks,
+ UpgradeKernelCmdlineArgTasks
+)
+
+NET_NAMING_SYSATTRS_RPM_NAME = 'rhel-net-naming-sysattrs'
+
+
+def is_net_scheme_compatible_with_current_cmdline():
+ kernel_cmdline = next(api.consume(KernelCmdline), None)
+ if not kernel_cmdline:
+ # Super unlikely
+ raise StopActorExecutionError('Did not receive any KernelCmdline messages.')
+
+ allows_predictable_names = True
+ already_has_a_net_naming_scheme = False
+ for param in kernel_cmdline.parameters:
+ if param.key == 'net.ifnames':
+ if param.value == '0':
+ allows_predictable_names = False
+ elif param.value == '1':
+ allows_predictable_names = True
+ if param.key == 'net.naming-scheme':
+ # We assume that the kernel cmdline does not contain invalid entries, namely,
+ # that the net.naming-scheme refers to a valid scheme.
+ already_has_a_net_naming_scheme = True
+
+ is_compatible = allows_predictable_names and not already_has_a_net_naming_scheme
+
+ msg = ('Should net.naming-scheme be added to kernel cmdline: %s. '
+ 'Reason: allows_predictable_names=%s, already_has_a_net_naming_scheme=%s')
+ api.current_logger().info(msg, 'yes' if is_compatible else 'no',
+ allows_predictable_names,
+ already_has_a_net_naming_scheme)
+
+ return is_compatible
+
+
+def emit_msgs_to_use_net_naming_schemes():
+ if get_env('LEAPP_USE_NET_NAMING_SCHEMES', '0') != '1' and version.get_target_major_version() != '8':
+ return
+
+ # The package should be installed regardless of whether we will modify the cmdline -
+ # if the cmdline already contains net.naming-scheme, then the package will be useful
+ # in both, the upgrade environment and on the target system.
+ pkgs_to_install = [NET_NAMING_SYSATTRS_RPM_NAME]
+ api.produce(TargetUserSpaceUpgradeTasks(install_rpms=pkgs_to_install))
+ api.produce(RpmTransactionTasks(to_install=pkgs_to_install))
+
+ if not is_net_scheme_compatible_with_current_cmdline():
+ return
+
+ naming_scheme = 'rhel-{0}.0'.format(version.get_source_major_version())
+ cmdline_args = [KernelCmdlineArg(key='net.naming-scheme', value=naming_scheme)]
+ api.produce(UpgradeKernelCmdlineArgTasks(to_add=cmdline_args))
+ api.produce(TargetKernelCmdlineArgTasks(to_add=cmdline_args))
diff --git a/repos/system_upgrade/el8toel9/actors/emit_net_naming_scheme/tests/test_emit_net_naming_scheme.py b/repos/system_upgrade/el8toel9/actors/emit_net_naming_scheme/tests/test_emit_net_naming_scheme.py
new file mode 100644
index 00000000..7a5eeba5
--- /dev/null
+++ b/repos/system_upgrade/el8toel9/actors/emit_net_naming_scheme/tests/test_emit_net_naming_scheme.py
@@ -0,0 +1,95 @@
+import pytest
+
+from leapp.libraries.actor import emit_net_naming as emit_net_naming_lib
+from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked
+from leapp.libraries.stdlib import api
+from leapp.models import (
+ KernelCmdline,
+ KernelCmdlineArg,
+ RpmTransactionTasks,
+ TargetKernelCmdlineArgTasks,
+ TargetUserSpaceUpgradeTasks,
+ UpgradeKernelCmdlineArgTasks
+)
+
+
+@pytest.mark.parametrize(
+ ('kernel_args', 'should_be_compatible'),
+ [
+ ([KernelCmdlineArg(key='net.naming-scheme', value='rhel-8.10')], False),
+ ([KernelCmdlineArg(key='net.ifnames', value='1')], True),
+ ([KernelCmdlineArg(key='net.ifnames', value='0')], False),
+ (
+ [
+ KernelCmdlineArg(key='net.naming-scheme', value='rhel-8.10'),
+ KernelCmdlineArg(key='net.ifname', value='0'),
+ KernelCmdlineArg(key='root', value='/dev/vda1')
+ ],
+ False
+ ),
+ ([KernelCmdlineArg(key='root', value='/dev/vda1')], True),
+ ]
+)
+def test_is_net_scheme_compatible_with_current_cmdline(monkeypatch, kernel_args, should_be_compatible):
+ kernel_cmdline = KernelCmdline(parameters=kernel_args)
+
+ def mocked_consume(msg_type):
+ yield {KernelCmdline: kernel_cmdline}[msg_type]
+
+ monkeypatch.setattr(api, 'consume', mocked_consume)
+
+ assert emit_net_naming_lib.is_net_scheme_compatible_with_current_cmdline() == should_be_compatible, \
+ [(arg.key, arg.value) for arg in kernel_cmdline.parameters]
+
+
+@pytest.mark.parametrize(
+ ('is_net_scheme_enabled', 'is_current_cmdline_compatible'),
+ [
+ (True, True),
+ (True, False),
+ (False, True)
+ ]
+)
+def test_emit_msgs_to_use_net_naming_schemes(monkeypatch, is_net_scheme_enabled, is_current_cmdline_compatible):
+ envvar_value = '1' if is_net_scheme_enabled else '0'
+
+ mocked_actor = CurrentActorMocked(src_ver='8.10',
+ dst_ver='9.5',
+ envars={'LEAPP_USE_NET_NAMING_SCHEMES': envvar_value})
+ monkeypatch.setattr(api, 'current_actor', mocked_actor)
+
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+ monkeypatch.setattr(emit_net_naming_lib,
+ 'is_net_scheme_compatible_with_current_cmdline',
+ lambda: is_current_cmdline_compatible)
+
+ emit_net_naming_lib.emit_msgs_to_use_net_naming_schemes()
+
+ def ensure_one_msg_of_type_produced(produced_messages, msg_type):
+ msgs = (msg for msg in produced_messages if isinstance(msg, msg_type))
+ msg = next(msgs)
+ assert not next(msgs, None), 'More than one message of type {type} produced'.format(type=type)
+ return msg
+
+ produced_messages = api.produce.model_instances
+ if is_net_scheme_enabled:
+ userspace_tasks = ensure_one_msg_of_type_produced(produced_messages, TargetUserSpaceUpgradeTasks)
+ assert userspace_tasks.install_rpms == [emit_net_naming_lib.NET_NAMING_SYSATTRS_RPM_NAME]
+
+ rpm_tasks = ensure_one_msg_of_type_produced(produced_messages, RpmTransactionTasks)
+ assert rpm_tasks.to_install == [emit_net_naming_lib.NET_NAMING_SYSATTRS_RPM_NAME]
+ else:
+ assert not api.produce.called
+ return
+
+ upgrade_cmdline_mods = (msg for msg in produced_messages if isinstance(msg, UpgradeKernelCmdlineArgTasks))
+ target_cmdline_mods = (msg for msg in produced_messages if isinstance(msg, TargetKernelCmdlineArgTasks))
+
+ if is_current_cmdline_compatible:
+ # We should emit cmdline modifications - both UpgradeKernelCmdlineArgTasks and TargetKernelCmdlineArgTasks
+ # should be produced
+ assert next(upgrade_cmdline_mods, None)
+ assert next(target_cmdline_mods, None)
+ else:
+ assert not next(upgrade_cmdline_mods, None)
+ assert not next(target_cmdline_mods, None)
--
2.47.0