From 5f569408469d4e43ff559c5b90c3cc068d59d3a4 Mon Sep 17 00:00:00 2001 From: Michal Hecko Date: Fri, 2 May 2025 16:35:18 +0200 Subject: [PATCH 32/37] models(ipuconfig): extend Version class to contain virtual versions Add virtual_{source,target}_version fields to the Version model. These fields store a virtual MAJOR.MINOR CentOS version so that version-specific checks originally written for RHEL can work as expected also on CentOS. On non-CentOS system, the value of these fields should be the same as source/target versions. The ipuworkflowconfig actor is modified accordingly to populate the newly added fields. Jira-ref: RHEL-80334 --- .../checksaphana/tests/test_checksaphana.py | 4 ++ .../libraries/ipuworkflowconfig.py | 51 +++++++++++++++++-- .../tests/test_ipuworkflowconfig.py | 41 ++++++++++++++- .../common/libraries/config/mock_configs.py | 16 ++++-- .../libraries/config/tests/test_version.py | 12 +++-- .../common/libraries/testutils.py | 13 ++++- .../system_upgrade/common/models/ipuconfig.py | 15 ++++++ 7 files changed, 137 insertions(+), 15 deletions(-) diff --git a/repos/system_upgrade/common/actors/checksaphana/tests/test_checksaphana.py b/repos/system_upgrade/common/actors/checksaphana/tests/test_checksaphana.py index 1417b00a..1e43f403 100644 --- a/repos/system_upgrade/common/actors/checksaphana/tests/test_checksaphana.py +++ b/repos/system_upgrade/common/actors/checksaphana/tests/test_checksaphana.py @@ -181,6 +181,8 @@ class MockSAPHanaVersionInstance(object): ) ) def test_checksaphana__fullfills_rhel86_hana_min_version(monkeypatch, major, rev, patchlevel, result): + monkeypatch.setattr(checksaphana.api, 'current_actor', testutils.CurrentActorMocked()) + monkeypatch.setattr(version, 'get_target_major_version', lambda: '8') monkeypatch.setattr(version, 'get_target_version', lambda: '8.6') monkeypatch.setattr(checksaphana, 'SAP_HANA_RHEL86_REQUIRED_PATCH_LEVELS', ((4, 48, 2), (5, 52, 0))) @@ -213,6 +215,8 @@ def test_checksaphana__fullfills_rhel86_hana_min_version(monkeypatch, major, rev ) ) def test_checksaphana__fullfills_hana_rhel90_min_version(monkeypatch, major, rev, patchlevel, result): + monkeypatch.setattr(checksaphana.api, 'current_actor', testutils.CurrentActorMocked()) + monkeypatch.setattr(version, 'get_target_major_version', lambda: '9') monkeypatch.setattr(version, 'get_target_version', lambda: '9.0') monkeypatch.setattr(checksaphana, 'SAP_HANA_RHEL90_REQUIRED_PATCH_LEVELS', ((5, 59, 4), (6, 63, 0))) diff --git a/repos/system_upgrade/common/actors/ipuworkflowconfig/libraries/ipuworkflowconfig.py b/repos/system_upgrade/common/actors/ipuworkflowconfig/libraries/ipuworkflowconfig.py index 35f61669..f76677fd 100644 --- a/repos/system_upgrade/common/actors/ipuworkflowconfig/libraries/ipuworkflowconfig.py +++ b/repos/system_upgrade/common/actors/ipuworkflowconfig/libraries/ipuworkflowconfig.py @@ -10,6 +10,7 @@ ENV_IGNORE = ('LEAPP_CURRENT_PHASE', 'LEAPP_CURRENT_ACTOR', 'LEAPP_VERBOSE', 'LEAPP_DEBUG') ENV_MAPPING = {'LEAPP_DEVEL_DM_DISABLE_UDEV': 'DM_DISABLE_UDEV'} +CENTOS_VIRTUAL_VERSIONS_KEY = '_virtual_versions' def get_env_vars(): @@ -92,8 +93,7 @@ def load_upgrade_paths_definitions(paths_definition_file): return definitions -def load_raw_upgrade_paths_for_distro_and_flavour(distro_id, flavour, paths_definition_file='upgrade_paths.json'): - all_definitions = load_upgrade_paths_definitions(paths_definition_file) +def extract_upgrade_paths_for_distro_and_flavour(all_definitions, distro_id, flavour): raw_upgrade_paths_for_distro = all_definitions.get(distro_id, {}) if not raw_upgrade_paths_for_distro: @@ -117,6 +117,39 @@ def construct_models_for_paths_matching_source_major(raw_paths, src_major_versio return multipaths_matching_source +def construct_virtual_versions(all_upgrade_path_defs, distro_id, source_version, target_version): + if distro_id.lower() != 'centos': + return (source_version, target_version) + + centos_upgrade_paths = all_upgrade_path_defs.get('centos', {}) + if not centos_upgrade_paths: + raise StopActorExecutionError('There are no upgrade paths defined for CentOS.') + + virtual_versions = centos_upgrade_paths.get(CENTOS_VIRTUAL_VERSIONS_KEY, {}) + if not virtual_versions: # Unlikely, only if using old upgrade_paths.json, but the user should not touch the file + details = {'details': 'The file does not contain any information about virtual versions of CentOS'} + raise StopActorExecutionError('The internal upgrade_paths.json file is malformed.') + + source_virtual_version = virtual_versions.get(source_version) + target_virtual_version = virtual_versions.get(target_version) + + if not source_virtual_version or not target_virtual_version: + if not source_virtual_version and not target_virtual_version: + what_is_missing = 'CentOS {} (source) and CentOS {} (target)'.format(source_virtual_version, + target_virtual_version) + elif not source_virtual_version: + what_is_missing = 'CentOS {} (source)'.format(source_virtual_version) + else: + what_is_missing = 'CentOS {} (target)'.format(target_virtual_version) + + details_msg = 'The {} field in upgrade path definitions does not provide any information for {}' + details = {'details': details_msg.format(CENTOS_VIRTUAL_VERSIONS_KEY, what_is_missing)} + raise StopActorExecutionError('Failed to identify virtual minor version number for the system.', + details=details) + + return (source_virtual_version, target_virtual_version) + + def produce_ipu_config(actor): flavour = os.environ.get('LEAPP_UPGRADE_PATH_FLAVOUR') target_version = os.environ.get('LEAPP_UPGRADE_PATH_TARGET_RELEASE') @@ -125,17 +158,27 @@ def produce_ipu_config(actor): check_target_major_version(source_version, target_version) - raw_upgrade_paths = load_raw_upgrade_paths_for_distro_and_flavour(os_release.release_id, flavour) + all_upgrade_path_defs = load_upgrade_paths_definitions('upgrade_paths.json') + raw_upgrade_paths = extract_upgrade_paths_for_distro_and_flavour(all_upgrade_path_defs, + os_release.release_id, + flavour) source_major_version = source_version.split('.')[0] exposed_supported_paths = construct_models_for_paths_matching_source_major(raw_upgrade_paths, source_major_version) + virtual_source_version, virtual_target_version = construct_virtual_versions(all_upgrade_path_defs, + os_release.release_id, + source_version, + target_version) + actor.produce(IPUConfig( leapp_env_vars=get_env_vars(), os_release=os_release, architecture=platform.machine(), version=Version( source=source_version, - target=target_version + target=target_version, + virtual_source_version=virtual_source_version, + virtual_target_version=virtual_target_version, ), kernel=get_booted_kernel(), flavour=flavour, diff --git a/repos/system_upgrade/common/actors/ipuworkflowconfig/tests/test_ipuworkflowconfig.py b/repos/system_upgrade/common/actors/ipuworkflowconfig/tests/test_ipuworkflowconfig.py index d88424ce..6184121b 100644 --- a/repos/system_upgrade/common/actors/ipuworkflowconfig/tests/test_ipuworkflowconfig.py +++ b/repos/system_upgrade/common/actors/ipuworkflowconfig/tests/test_ipuworkflowconfig.py @@ -149,7 +149,44 @@ def test_load_raw_upgrade_paths_for_distro_and_flavour(monkeypatch, distro, flav } } } - monkeypatch.setattr(ipuworkflowconfig, 'load_upgrade_paths_definitions', lambda *args: defined_upgrade_paths) - result = ipuworkflowconfig.load_raw_upgrade_paths_for_distro_and_flavour(distro, flavour) + result = ipuworkflowconfig.extract_upgrade_paths_for_distro_and_flavour(defined_upgrade_paths, + distro, flavour) assert result == expected_result + + +@pytest.mark.parametrize( + ('construction_params', 'expected_versions'), + [ + (('centos', '8', '9'), ('8.10', '9.5')), + (('rhel', '8.10', '9.4'), ('8.10', '9.4')), + ] +) +def test_virtual_version_construction(construction_params, expected_versions): + defined_upgrade_paths = { + 'rhel': { + 'default': { + '8.10': ['9.4', '9.5', '9.6'], + '8.4': ['9.2'], + '9.6': ['10.0'], + '8': ['9.4', '9.5', '9.6'], + '9': ['10.0'] + }, + 'saphana': { + '8.10': ['9.6', '9.4'], + '8': ['9.6', '9.4'], + '9.6': ['10.0'], + '9': ['10.0'] + } + }, + 'centos': { + '8': ['9'], + '_virtual_versions': { + '8': '8.10', + '9': '9.5', + } + }, + } + + result = ipuworkflowconfig.construct_virtual_versions(defined_upgrade_paths, *construction_params) + assert result == expected_versions diff --git a/repos/system_upgrade/common/libraries/config/mock_configs.py b/repos/system_upgrade/common/libraries/config/mock_configs.py index a1c9c0fd..a7ee0000 100644 --- a/repos/system_upgrade/common/libraries/config/mock_configs.py +++ b/repos/system_upgrade/common/libraries/config/mock_configs.py @@ -19,7 +19,9 @@ CONFIG = IPUConfig( ), version=Version( source='7.6', - target='8.0' + target='8.0', + virtual_source_version='7.6', + virtual_target_version='8.0' ), architecture='x86_64', kernel='3.10.0-957.43.1.el7.x86_64', @@ -39,7 +41,9 @@ CONFIG_NO_NETWORK_RENAMING = IPUConfig( ), version=Version( source='7.6', - target='8.0' + target='8.0', + virtual_source_version='7.6', + virtual_target_version='8.0' ), architecture='x86_64', kernel='3.10.0-957.43.1.el7.x86_64', @@ -59,7 +63,9 @@ CONFIG_ALL_SIGNED = IPUConfig( ), version=Version( source='7.6', - target='8.0' + target='8.0', + virtual_source_version='7.6', + virtual_target_version='8.0' ), architecture='x86_64', kernel='3.10.0-957.43.1.el7.x86_64', @@ -78,7 +84,9 @@ CONFIG_S390X = IPUConfig( ), version=Version( source='7.6', - target='8.0' + target='8.0', + virtual_source_version='7.6', + virtual_target_version='8.0' ), architecture='s390x', kernel='3.10.0-957.43.1.el7.x86_64', diff --git a/repos/system_upgrade/common/libraries/config/tests/test_version.py b/repos/system_upgrade/common/libraries/config/tests/test_version.py index 3cb6479c..a8a1023e 100644 --- a/repos/system_upgrade/common/libraries/config/tests/test_version.py +++ b/repos/system_upgrade/common/libraries/config/tests/test_version.py @@ -27,7 +27,9 @@ def test_cmp_versions(): assert not version._cmp_versions(['>= 7.6', '& 7.7']) -def test_matches_version_wrong_args(): +def test_matches_version_wrong_args(monkeypatch): + monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) + with pytest.raises(TypeError): version.matches_version('>= 7.6', '7.7') with pytest.raises(TypeError): @@ -42,7 +44,9 @@ def test_matches_version_wrong_args(): version.matches_version(['>= 7.6', '& 7.7'], '7.7') -def test_matches_version_fail(): +def test_matches_version_fail(monkeypatch): + monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) + assert not version.matches_version(['> 7.6', '< 7.7'], '7.6') assert not version.matches_version(['> 7.6', '< 7.7'], '7.7') assert not version.matches_version(['> 7.6', '< 7.10'], '7.6') @@ -50,7 +54,9 @@ def test_matches_version_fail(): assert not version.matches_version(['7.6', '7.7', '7.10'], '7.8') -def test_matches_version_pass(): +def test_matches_version_pass(monkeypatch): + monkeypatch.setattr(api, 'current_actor', CurrentActorMocked()) + assert version.matches_version(['7.6', '7.7', '7.10'], '7.7') assert version.matches_version(['> 7.6', '< 7.10'], '7.7') diff --git a/repos/system_upgrade/common/libraries/testutils.py b/repos/system_upgrade/common/libraries/testutils.py index 1b3c3683..3e145d91 100644 --- a/repos/system_upgrade/common/libraries/testutils.py +++ b/repos/system_upgrade/common/libraries/testutils.py @@ -74,15 +74,24 @@ def _make_default_config(actor_config_schema): return _normalize_config({}, merged_schema) # Will fill default values during normalization +# Note: The constructor of the following class takes in too many arguments (R0913). A builder-like +# pattern would be nice here. Ideally, the builder should actively prevent the developer from setting fields +# that do not affect actor's behavior in __setattr__. class CurrentActorMocked(object): # pylint:disable=R0904 - def __init__(self, arch=architecture.ARCH_X86_64, envars=None, kernel='3.10.0-957.43.1.el7.x86_64', + def __init__(self, arch=architecture.ARCH_X86_64, envars=None, # pylint:disable=R0913 + kernel='3.10.0-957.43.1.el7.x86_64', release_id='rhel', src_ver='7.8', dst_ver='8.1', msgs=None, flavour='default', config=None, + virtual_source_version=None, virtual_target_version=None, supported_upgrade_paths=None): """ :param List[IPUSourceToPossibleTargets] supported_upgrade_paths: List of supported upgrade paths. """ envarsList = [EnvVar(name=k, value=v) for k, v in envars.items()] if envars else [] - version = namedtuple('Version', ['source', 'target'])(src_ver, dst_ver) + + version_fields = ['source', 'target', 'virtual_source_version', 'virtual_target_version'] + version_values = [src_ver, dst_ver, virtual_source_version or src_ver, virtual_target_version or dst_ver] + version = namedtuple('Version', version_fields)(*version_values) + release = namedtuple('OS_release', ['release_id', 'version_id'])(release_id, src_ver) self._common_folder = '../../files' diff --git a/repos/system_upgrade/common/models/ipuconfig.py b/repos/system_upgrade/common/models/ipuconfig.py index 0a16b603..379ac13f 100644 --- a/repos/system_upgrade/common/models/ipuconfig.py +++ b/repos/system_upgrade/common/models/ipuconfig.py @@ -33,6 +33,21 @@ class Version(Model): target = fields.String() """Version of the target system. E.g. '8.2.'.""" + virtual_source_version = fields.String() + """ + Source OS version used when checking whether to execute version-dependent code. + + On RHEL and other systems that have version of the form MINOR.MAJOR, `virtual_source_version` + matches `source_version`. + + CentOS has version of the form MAJOR, lacking the minor version number. The + `virtual_source_version` value is obtained by combining CentOS major + version number with a minor version number stored internally in the upgrade_paths.json file. + """ + + virtual_target_version = fields.String() + """ See :py:attr:`virtual_source_version` """ + class IPUSourceToPossibleTargets(Model): """ -- 2.49.0