Skip to content

Commit

Permalink
CheckInstalledKernels: Compare kernels by version number too
Browse files Browse the repository at this point in the history
Actor CheckInstalledKernels only checks releases of kernels and not the
version numbers. If there are two kernels where one has higher version
number and the other one a higher release number (for example
3.10.0-957.41.1.el7.ppc64le and 4.14.0-115.29.1.el7a.ppc64le), the wrong
one is evaluated as newer, which can incorrectly inhibit the upgrade.

This commit introduces version number checks into the actor. Version is
always prioritised before release when ordering kernels.
Additionally, versions are internally normalized to exactly three
integers to avoid comparison quirks, should a version for some reason
consist of less or more than three integers.
  • Loading branch information
drehak authored and pirat89 committed Sep 30, 2020
1 parent dce08fc commit dbcb772
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,40 @@
from leapp.models import InstalledRedHatSignedRPM


def _normalize_version(version):
if len(version) != 3:
if len(version) > 3:
api.current_logger().debug('Version {} has more than three components, trimming'.format(version))
del version[3:]
elif len(version) < 3:
api.current_logger().debug('Version {} has less than three components, padding'.format(version))
while len(version) < 3:
version.append(0)
api.current_logger().debug('Normalised version to {}'.format(version))
return version # could be omitted but it's useful for nesting calls


def get_current_kernel_version():
"""
Get the version of the running kernel as a tuple of three integers.
"""
kernel = api.current_actor().configuration.kernel
return tuple(_normalize_version(list(
map(int, kernel.split('-')[0].split('.'))
)))


def get_kernel_rpm_version(rpm):
"""
Get the version of a kernel RPM as a tuple of three integers.
:param rpm: An instance of an RPM derived model.
"""
return tuple(_normalize_version(list(
map(int, rpm.version.split('.'))
)))


def get_current_kernel_release():
"""
Get the release of the running kernel as an integer.
Expand All @@ -24,10 +58,11 @@ def get_kernel_rpm_release(rpm):

def get_kernel_rpms():
"""
Get all installed kernel packages ordered by release number (ascending).
Get all installed kernel packages ordered first by version, then release number (ascending).
"""
rpms = next(api.consume(InstalledRedHatSignedRPM), InstalledRedHatSignedRPM())
return sorted([pkg for pkg in rpms.items if pkg.name == 'kernel'], key=get_kernel_rpm_release)
return sorted([pkg for pkg in rpms.items if pkg.name == 'kernel'],
key=lambda k: (get_kernel_rpm_version(k), get_kernel_rpm_release(k)))


def process():
Expand Down Expand Up @@ -57,8 +92,15 @@ def process():
reporting.RelatedResource('package', 'kernel')
])

current = get_current_kernel_release()
if current != get_kernel_rpm_release(pkgs[-1]):
newest = pkgs[-1]
newest_release = get_kernel_rpm_release(newest)
newest_version = get_kernel_rpm_version(newest)
current_release = get_current_kernel_release()
current_version = get_current_kernel_version()
api.current_logger().debug('Current kernel: V {}, R {}'.format(current_version, current_release))
api.current_logger().debug('Newest kernel: V {}, R {}'.format(newest_version, newest_release))

if newest_release != current_release or newest_version != current_version:
title = 'Newest installed kernel not in use'
summary = ('To ensure a stable upgrade, the machine needs to be'
' booted into the latest installed kernel.')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pytest

from leapp import reporting
from leapp.libraries.actor import checkinstalledkernels
from leapp.libraries.common.config import architecture
Expand All @@ -10,29 +12,67 @@

def mocked_consume(pkgs): # pkgs = [(name, version-number)]
installed_rpms = []
version = 1
for pkg in pkgs:
installed_rpms.append(
RPM(
name=pkg[0],
arch='noarch',
version=str(version),
release='{}.sm01'.format(pkg[1]),
version=pkg[1],
release='{}.sm01'.format(pkg[2]),
epoch='0',
packager=RH_PACKAGER,
pgpsig='SOME_OTHER_SIG_X',
)
)
version += 1

def f(*a):
yield InstalledRedHatSignedRPM(items=installed_rpms)

return f


s390x_pkgs_single = [('kernel', 957), ('something', 957), ('kernel-something', 957)]
s390x_pkgs_multi = [('kernel', 957), ('something', 957), ('kernel', 956)]
@pytest.mark.parametrize('version,expected', [
([], [0, 0, 0]),
([1], [1, 0, 0]),
([1, 2], [1, 2, 0]),
([1, 2, 3], [1, 2, 3]),
([1, 2, 3, 4], [1, 2, 3]),
([1, 2, 3, 4, 5], [1, 2, 3])
])
def test_normalize(version, expected):
assert checkinstalledkernels._normalize_version(version) == expected


@pytest.mark.parametrize('vra,version,release', [
('3.10.0-1234.21.1.el7.x86_64', (3, 10, 0), 1234),
('5.8.8-100.fc31.x86_64', (5, 8, 8), 100),
])
def test_current_kernel(monkeypatch, vra, version, release):
monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(kernel=vra))
assert version == checkinstalledkernels.get_current_kernel_version()
assert release == checkinstalledkernels.get_current_kernel_release()


@pytest.mark.parametrize('version_string, release_string, version, release', [
('3.10.0', '1234.21.1.el7', (3, 10, 0), 1234),
('5.8.8', '100.fc31', (5, 8, 8), 100),
])
def test_kernel_rpm(version_string, release_string, version, release):
rpm = RPM(
name='kernel',
arch='noarch',
version=version_string,
release=release_string,
epoch='0',
packager=RH_PACKAGER,
pgpsig='SOME_OTHER_SIG_X',
)
assert version == checkinstalledkernels.get_kernel_rpm_version(rpm)
assert release == checkinstalledkernels.get_kernel_rpm_release(rpm)


s390x_pkgs_single = [('kernel', '3.10.0', 957), ('something', '3.10.0', 957), ('kernel-something', '3.10.0', 957)]
s390x_pkgs_multi = [('kernel', '3.10.0', 957), ('something', '3.10.0', 957), ('kernel', '3.10.0', 956)]


def test_single_kernel_s390x(monkeypatch):
Expand Down Expand Up @@ -64,7 +104,7 @@ def test_multi_kernel_s390x(monkeypatch):
assert reporting.create_report.report_fields['title'] == 'Multiple kernels installed'


versioned_kernel_pkgs = [('kernel', 456), ('kernel', 789), ('kernel', 1234)]
versioned_kernel_pkgs = [('kernel', '3.10.0', 456), ('kernel', '3.10.0', 789), ('kernel', '3.10.0', 1234)]


def test_newest_kernel(monkeypatch):
Expand All @@ -90,3 +130,21 @@ def test_newest_kernel(monkeypatch):
checkinstalledkernels.process()
assert reporting.create_report.called
assert reporting.create_report.report_fields['title'] == 'Newest installed kernel not in use'

# put the kernel in the middle of the list so that its position doesn't guarantee its rank
versioned_kernel_pkgs.insert(2, ('kernel', '4.14.0', 115))

monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(kernel='4.14.0-115.29.1.el7a.ppc64le'))
monkeypatch.setattr(api, 'current_logger', logger_mocked())
monkeypatch.setattr(api, 'consume', mocked_consume(versioned_kernel_pkgs))
monkeypatch.setattr(reporting, 'create_report', create_report_mocked())
checkinstalledkernels.process()
assert not reporting.create_report.called

monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(kernel='3.10.0-1234.21.1.el7.x86_64'))
monkeypatch.setattr(api, 'current_logger', logger_mocked())
monkeypatch.setattr(api, 'consume', mocked_consume(versioned_kernel_pkgs))
monkeypatch.setattr(reporting, 'create_report', create_report_mocked())
checkinstalledkernels.process()
assert reporting.create_report.called
assert reporting.create_report.report_fields['title'] == 'Newest installed kernel not in use'

0 comments on commit dbcb772

Please sign in to comment.