Skip to content

Commit 0a5f66e

Browse files
tomasfratrikpirat89
authored andcommitted
Verify supported target OS version in actors
Originally when user specified the target system release using `--target` CLI option the verification has been performed immediately as only supported releases have been listed as possible choices for this option. The benefit of this solution was that users did not have to wait for all other checks to realize they execute leapp probably incorrectly. Unfortunately, * number of users do not understand why only some versions are supported * users upgrading with via various webUIs presenting only leapp reports could not see the error message available in terminal To resolve this problem the checks are moved into actors so in case of specified unsupported target version the information is present in generated leapp reports. Current behaviour is like this: * in case of invalid input (incorrect format of input data) the hard error is raised as before immediately. Malformed input data will not be processed anyhow by any actors * report error when the specified target major version is not direct successor of the current system version. I.e. specify 10.0 when upgrading from RHEL 8 (only RHEL 9 is acceptable). * this prevents number of cryptic errors as actors are not prepared for this situation * report standard inhibitor if the target release is not in the defined upgrade path, unless LEAPP_UNSUPPORTED=1 * running leapp in unsupported (devel) mode skips the inhibitor and entire report Additional changes: * Update error message when format of target version is incorrect to clarify the expected version format jira: RHEL-51072 Co-authored-by: Petr Stodulk <[email protected]>
1 parent a643882 commit 0a5f66e

File tree

7 files changed

+229
-14
lines changed

7 files changed

+229
-14
lines changed

commands/command_utils.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ def check_version(version):
2828
:return: release tuple
2929
"""
3030
if not re.match(VERSION_REGEX, version):
31-
raise CommandError('Unexpected format of target version: {}'.format(version))
31+
raise CommandError(
32+
"Unexpected format of target version: {}. "
33+
"The required format is 'X.Y' (major and minor version).".format(version)
34+
)
3235
return version.split('.')
3336

3437

@@ -126,7 +129,6 @@ def vet_upgrade_path(args):
126129
Make sure the user requested upgrade_path is a supported one.
127130
If LEAPP_DEVEL_TARGET_RELEASE is set then it's value is not vetted against upgrade_paths_map but used as is.
128131
129-
:raises: `CommandError` if the specified upgrade_path is not supported
130132
:return: `tuple` (target_release, flavor)
131133
"""
132134
flavor = get_upgrade_flavour()
@@ -135,13 +137,6 @@ def vet_upgrade_path(args):
135137
check_version(env_version_override)
136138
return (env_version_override, flavor)
137139
target_release = args.target or get_target_version(flavor)
138-
supported_target_versions = get_supported_target_versions(flavor)
139-
if target_release not in supported_target_versions:
140-
raise CommandError(
141-
"Upgrade to {to} for {flavor} upgrade path is not supported, possible choices are {choices}".format(
142-
to=target_release,
143-
flavor=flavor,
144-
choices=','.join(supported_target_versions)))
145140
return (target_release, flavor)
146141

147142

commands/preupgrade/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@
2828
choices=['ga', 'e4s', 'eus', 'aus'],
2929
value_type=str.lower) # This allows the choices to be case insensitive
3030
@command_opt('iso', help='Use provided target RHEL installation image to perform the in-place upgrade.')
31-
@command_opt('target', choices=command_utils.get_supported_target_versions(),
32-
help='Specify RHEL version to upgrade to for {} detected upgrade flavour'.format(
31+
@command_opt('target', help='Specify RHEL version to upgrade to for {} detected upgrade flavour'.format(
3332
command_utils.get_upgrade_flavour()))
3433
@command_opt('report-schema', help='Specify report schema version for leapp-report.json',
3534
choices=['1.0.0', '1.1.0', '1.2.0'], default=get_config().get('report', 'schema'))

commands/upgrade/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@
3434
choices=['ga', 'e4s', 'eus', 'aus'],
3535
value_type=str.lower) # This allows the choices to be case insensitive
3636
@command_opt('iso', help='Use provided target RHEL installation image to perform the in-place upgrade.')
37-
@command_opt('target', choices=command_utils.get_supported_target_versions(),
38-
help='Specify RHEL version to upgrade to for {} detected upgrade flavour'.format(
37+
@command_opt('target', help='Specify RHEL version to upgrade to for {} detected upgrade flavour'.format(
3938
command_utils.get_upgrade_flavour()))
4039
@command_opt('report-schema', help='Specify report schema version for leapp-report.json',
4140
choices=['1.0.0', '1.1.0', '1.2.0'], default=get_config().get('report', 'schema'))
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from leapp.actors import Actor
2+
from leapp.libraries.actor import checktargetversion
3+
from leapp.models import IPUPaths
4+
from leapp.reporting import Report
5+
from leapp.tags import ChecksPhaseTag, IPUWorkflowTag
6+
7+
8+
class CheckTargetVersion(Actor):
9+
"""
10+
Check that the target system version is supported by the upgrade process.
11+
12+
Invoke inhibitor if the target system is not supported.
13+
Allow unsupported target if `LEAPP_UNSUPPORTED=1` is set.
14+
"""
15+
16+
name = 'check_target_version'
17+
consumes = (IPUPaths,)
18+
produces = (Report,)
19+
tags = (ChecksPhaseTag, IPUWorkflowTag)
20+
21+
def process(self):
22+
checktargetversion.process()
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
from leapp import reporting
2+
from leapp.exceptions import StopActorExecutionError
3+
from leapp.libraries.common.config import get_env, version
4+
from leapp.libraries.stdlib import api
5+
from leapp.models import IPUPaths
6+
from leapp.utils.deprecation import suppress_deprecation
7+
8+
FMT_LIST_SEPARATOR = '\n - '
9+
10+
11+
@suppress_deprecation(IPUPaths)
12+
def get_supported_target_versions():
13+
ipu_paths = next(api.consume(IPUPaths), None)
14+
src_version = version.get_source_version()
15+
if not ipu_paths:
16+
# NOTE: missing unit-tests. Unexpected situation and the solution
17+
# is possibly temporary
18+
raise StopActorExecutionError('Missing the IPUPaths message. Cannot determine defined upgrade paths.')
19+
for ipu_path in ipu_paths.data:
20+
if ipu_path.source_version == src_version:
21+
return ipu_path.target_versions
22+
23+
# Nothing discovered. Current src_version is not already supported or not yet.
24+
# Problem of supported source versions is handled now separately in other
25+
# actors. Fallbak from X.Y versioning to major version only.
26+
api.current_logger().warning(
27+
'Cannot discover support upgrade path for this system release: {}'
28+
.format(src_version)
29+
)
30+
maj_version = version.get_source_major_version()
31+
for ipu_path in ipu_paths.data:
32+
if ipu_path.source_version == maj_version:
33+
return ipu_path.target_versions
34+
35+
# Completely unknown
36+
api.current_logger().warning(
37+
'Cannot discover supported upgrade path for this system major version: {}'
38+
.format(maj_version)
39+
)
40+
return []
41+
42+
43+
def process():
44+
target_version = version.get_target_version()
45+
supported_target_versions = get_supported_target_versions()
46+
47+
if target_version in supported_target_versions:
48+
api.current_logger().info('Target version is supported. Continue.')
49+
return
50+
51+
if get_env('LEAPP_UNSUPPORTED', '0') == '1':
52+
api.current_logger().warning(
53+
'Upgrading to an unsupported version of the target system but LEAPP_UNSUPPORTED=1. Continue.'
54+
)
55+
return
56+
57+
# inhibit the upgrade - unsupported target and leapp running in production mode
58+
hint = (
59+
'Choose a supported version of the target OS for the upgrade.'
60+
' Alternatively, if you require to upgrade using an unsupported upgrade path,'
61+
' set the `LEAPP_UNSUPPORTED=1` environment variable to confirm you'
62+
' want to upgrade on your own risk.'
63+
)
64+
65+
reporting.create_report([
66+
reporting.Title('Specified version of the target system is not supported'),
67+
reporting.Summary(
68+
'The in-place upgrade to the specified version ({tgt_ver}) of the target system'
69+
' is not supported from the current system version. Follow the official'
70+
' documentation for up to date information about supported upgrade'
71+
' paths and future plans (see the attached link).'
72+
' The in-place upgrade is enabled to the following versions of the target system:{sep}{ver_list}'
73+
.format(
74+
sep=FMT_LIST_SEPARATOR,
75+
ver_list=FMT_LIST_SEPARATOR.join(supported_target_versions),
76+
tgt_ver=target_version
77+
)
78+
),
79+
reporting.Groups([reporting.Groups.INHIBITOR]),
80+
reporting.Severity(reporting.Severity.HIGH),
81+
reporting.Remediation(hint=hint),
82+
reporting.ExternalLink(
83+
url='https://access.redhat.com/articles/4263361',
84+
title='Supported in-place upgrade paths for Red Hat Enterprise Linux'
85+
)
86+
])
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import os
2+
3+
import pytest
4+
5+
from leapp import reporting
6+
from leapp.libraries.actor import checktargetversion
7+
from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, logger_mocked
8+
from leapp.libraries.stdlib import api
9+
from leapp.models import IPUPath, IPUPaths
10+
from leapp.utils.deprecation import suppress_deprecation
11+
from leapp.utils.report import is_inhibitor
12+
13+
14+
# It must be in a function so we can suppress the deprecation warning in tests.
15+
@suppress_deprecation(IPUPaths)
16+
def _get_upgrade_paths_data():
17+
return IPUPaths(data=[
18+
IPUPath(source_version='7.9', target_versions=['8.10']),
19+
IPUPath(source_version='8.10', target_versions=['9.4', '9.5', '9.6']),
20+
IPUPath(source_version='9.6', target_versions=['10.0']),
21+
IPUPath(source_version='7', target_versions=['8.10']),
22+
IPUPath(source_version='8', target_versions=['9.4', '9.5', '9.6']),
23+
IPUPath(source_version='9', target_versions=['10.0'])
24+
])
25+
26+
27+
@pytest.fixture
28+
def setup_monkeypatch(monkeypatch):
29+
"""Fixture to set up common monkeypatches."""
30+
31+
def _setup(source_version, target_version, leapp_unsupported='0'):
32+
curr_actor_mocked = CurrentActorMocked(
33+
src_ver=source_version,
34+
dst_ver=target_version,
35+
envars={'LEAPP_UNSUPPORTED': leapp_unsupported},
36+
msgs=[_get_upgrade_paths_data()]
37+
)
38+
monkeypatch.setattr(api, 'current_actor', curr_actor_mocked)
39+
monkeypatch.setattr(api, 'current_logger', logger_mocked())
40+
monkeypatch.setattr(reporting, 'create_report', create_report_mocked())
41+
return _setup
42+
43+
44+
@pytest.mark.parametrize(('source_version', 'target_version', 'leapp_unsupported'), [
45+
# LEAPP_UNSUPPORTED=0
46+
('7.9', '9.0', '0'),
47+
('8.10', '9.0', '0'),
48+
('9.6', '10.1', '0'),
49+
('7', '9.0', '0'),
50+
('8', '9.0', '0'),
51+
('9', '10.1', '0'),
52+
# LEAPP_UNSUPPORTED=1
53+
('7.9', '9.0', '1'),
54+
('8.10', '9.0', '1'),
55+
('9.6', '10.1', '1'),
56+
('7', '9.0', '1'),
57+
('8', '9.0', '1'),
58+
('9', '10.1', '1'),
59+
])
60+
def test_unsuppoted_paths(setup_monkeypatch, source_version, target_version, leapp_unsupported):
61+
setup_monkeypatch(source_version, target_version, leapp_unsupported)
62+
63+
if leapp_unsupported == '1':
64+
checktargetversion.process()
65+
assert reporting.create_report.called == 0
66+
assert api.current_logger.warnmsg
67+
else:
68+
checktargetversion.process()
69+
assert reporting.create_report.called == 1
70+
assert is_inhibitor(reporting.create_report.report_fields)
71+
72+
73+
@pytest.mark.parametrize(('source_version', 'target_version'), [
74+
('7.9', '8.10'),
75+
('8.10', '9.4'),
76+
('8.10', '9.5'),
77+
('8.10', '9.6'),
78+
('9.6', '10.0'),
79+
('7', '8.10'),
80+
('8', '9.4'),
81+
('8', '9.5'),
82+
('8', '9.6'),
83+
('9', '10.0'),
84+
])
85+
def test_supported_paths(setup_monkeypatch, source_version, target_version):
86+
setup_monkeypatch(source_version, target_version, leapp_unsupported='0')
87+
88+
checktargetversion.process()
89+
assert reporting.create_report.called == 0
90+
assert api.current_logger.infomsg

repos/system_upgrade/common/actors/ipuworkflowconfig/libraries/ipuworkflowconfig.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,41 @@ def get_os_release(path):
6464
details={'details': str(e)})
6565

6666

67+
def check_target_major_version(curr_version, target_version):
68+
required_major_version = int(curr_version.split('.')[0]) + 1
69+
specified_major_version = int(target_version.split('.')[0])
70+
if specified_major_version != required_major_version:
71+
raise StopActorExecutionError(
72+
message='Specified invalid major version of the target system',
73+
details={
74+
'Specified target major version': str(specified_major_version),
75+
'Required target major version': str(required_major_version),
76+
'hint': (
77+
'The in-place upgrade is possible only to the next system'
78+
' major version: {ver}. Specify a valid version of the'
79+
' target system when running leapp.'
80+
' For more information about supported in-place upgrade paths'
81+
' follow: https://access.redhat.com/articles/4263361'
82+
.format(ver=required_major_version)
83+
)
84+
}
85+
)
86+
87+
6788
def produce_ipu_config(actor):
6889
flavour = os.environ.get('LEAPP_UPGRADE_PATH_FLAVOUR')
6990
target_version = os.environ.get('LEAPP_UPGRADE_PATH_TARGET_RELEASE')
7091
os_release = get_os_release('/etc/os-release')
92+
source_version = os_release.version_id
93+
94+
check_target_major_version(source_version, target_version)
7195

7296
actor.produce(IPUConfig(
7397
leapp_env_vars=get_env_vars(),
7498
os_release=os_release,
7599
architecture=platform.machine(),
76100
version=Version(
77-
source=os_release.version_id,
101+
source=source_version,
78102
target=target_version
79103
),
80104
kernel=get_booted_kernel(),

0 commit comments

Comments
 (0)