From 4a1ac6cf2a0cdc17b5c928b0f40d20295bb467d5 Mon Sep 17 00:00:00 2001 From: mhecko Date: Mon, 29 Apr 2024 11:16:46 +0200 Subject: [PATCH] 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-.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 f400ebf82f..e4ecf39e25 100644 --- a/repos/system_upgrade/common/actors/addupgradebootentry/actor.py +++ b/repos/system_upgrade/common/actors/addupgradebootentry/actor.py @@ -8,11 +8,13 @@ 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 553ffc3547..b236e39b56 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 @@ 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 flatten_arguments(arg_pair): 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 flatten_arguments(arg_pair): 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 run_commands_adding_entry(extra_command_suffix=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 c4f5232bf8..2f58ba9edc 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 @@ BootContent, KernelCmdline, KernelCmdlineArg, + LateTargetKernelCmdlineArgTasks, LiveModeArtifacts, LiveModeConfig, TargetKernelCmdlineArgTasks @@ -82,8 +83,10 @@ def get_boot_file_paths_mocked(): 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 get_boot_file_paths_mocked(): 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 get_boot_file_paths_mocked(): 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 readlink_mock(path): 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 3585a14e46..6d5f39dd2e 100644 --- a/repos/system_upgrade/common/actors/kernelcmdlineconfig/actor.py +++ b/repos/system_upgrade/common/actors/kernelcmdlineconfig/actor.py @@ -3,7 +3,13 @@ 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 19c50f3cf9..98b8b95be6 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.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 dc5196ea5b..2f12742aa0 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 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 e3568a0af9..fafd285379 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 0000000000..769fe20bf7 --- /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 0000000000..65abdd4d42 --- /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 0000000000..7a5eeba529 --- /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)