Skip to content

Commit

Permalink
Add unit tests for leftover packages actors and move them to common repo
Browse files Browse the repository at this point in the history
    Actors:
        - checkleftoverpackages
        - removeleftoverpackages
        - reportleftoverpackages

    * Refactor actors code from el7toel8 to common
    * Put their processes into library
    * Create unit tests for actors

Jira: OAMG-1254
  • Loading branch information
tomasfratrik committed Apr 29, 2024
1 parent 346b741 commit b584b7f
Show file tree
Hide file tree
Showing 12 changed files with 433 additions and 164 deletions.
20 changes: 20 additions & 0 deletions repos/system_upgrade/common/actors/checkleftoverpackages/actor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from leapp.actors import Actor
from leapp.libraries.actor import checkleftoverpackages
from leapp.models import InstalledUnsignedRPM, LeftoverPackages, TransactionCompleted
from leapp.tags import IPUWorkflowTag, RPMUpgradePhaseTag


class CheckLeftoverPackages(Actor):
"""
Check if there are any RHEL 7 packages present after upgrade.
Actor produces message containing these packages. Message is empty if there are no el7 package left.
"""

name = 'check_leftover_packages'
consumes = (TransactionCompleted, InstalledUnsignedRPM)
produces = (LeftoverPackages,)
tags = (RPMUpgradePhaseTag, IPUWorkflowTag)

def process(self):
checkleftoverpackages.process()
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import re

from leapp.libraries.common.config.version import get_source_major_version
from leapp.libraries.common.rpms import get_installed_rpms
from leapp.libraries.stdlib import api
from leapp.models import InstalledUnsignedRPM, LeftoverPackages, RPM


def process():
LEAPP_PACKAGES = ['leapp', 'leapp-repository', 'snactor', 'leapp-repository-deps-el8', 'leapp-deps-el8',
'python2-leapp']
installed_rpms = get_installed_rpms()
if not installed_rpms:
return

to_remove = LeftoverPackages()
unsigned = [pkg.name for pkg in next(api.consume(InstalledUnsignedRPM), InstalledUnsignedRPM()).items]

for rpm in installed_rpms:
rpm = rpm.strip()
if not rpm:
continue
name, version, release, epoch, packager, arch, pgpsig = rpm.split('|')

version_pattern = r'el(\d+)'
match = re.search(version_pattern, release)

if match:
RHEL_version = match.group(1)
if int(RHEL_version) <= int(get_source_major_version()) and name not in LEAPP_PACKAGES + unsigned:
to_remove.items.append(RPM(
name=name,
version=version,
epoch=epoch,
packager=packager,
arch=arch,
release=release,
pgpsig=pgpsig
))

api.produce(to_remove)
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import pytest

from leapp.libraries.actor import checkleftoverpackages
from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked
from leapp.libraries.stdlib import api
from leapp.models import InstalledUnsignedRPM, LeftoverPackages, RPM


@pytest.mark.parametrize(
('source_major_version', 'rpm_name', 'release', 'expected_to_be_removed'),
(
(7, 'sed', '7.el7', True),
(8, 'sed', '7.el7', True),
(8, 'sed', '8.el7', True),
(7, 'leapp', '1.el7', False),
(7, 'unsigned', '1.el7', False),
(7, 'leapp-repository', '1.el8', False),
(7, 'gnutls', '8.el8_9.1', False),
)
)
def test_package_to_be_removed(monkeypatch, source_major_version, rpm_name, release, expected_to_be_removed):
rpm_details = {
'version': '0.1',
'epoch': '0',
'packager': 'packager',
'arch': 'noarch',
'pgpsig': 'sig'
}

def get_installed_rpms_mocked():
return ['{}|{}|{}|{}|{}|{}|{}'.format(rpm_name, rpm_details['version'], release, rpm_details['epoch'],
rpm_details['packager'], rpm_details['arch'], rpm_details['pgpsig'])]

UnsignedRPM = RPM(name='unsigned', version='0.1', release=release, epoch='0',
packager='packager', arch='noarch', pgpsig='OTHER_SIG')

monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[InstalledUnsignedRPM(items=[UnsignedRPM])]))
monkeypatch.setattr(checkleftoverpackages, 'get_installed_rpms', get_installed_rpms_mocked)
monkeypatch.setattr(checkleftoverpackages, 'get_source_major_version', lambda: source_major_version)
monkeypatch.setattr(api, 'produce', produce_mocked())

checkleftoverpackages.process()

expected_output = LeftoverPackages()
if expected_to_be_removed:
expected_output.items.append(RPM(name=rpm_name, release=release, **rpm_details))

assert api.produce.called == 1
assert api.produce.model_instances[0] == expected_output
22 changes: 22 additions & 0 deletions repos/system_upgrade/common/actors/removeleftoverpackages/actor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from leapp.actors import Actor
from leapp.libraries.actor import removeleftoverpackages
from leapp.models import LeftoverPackages, RemovedPackages
from leapp.reporting import Report
from leapp.tags import ExperimentalTag, IPUWorkflowTag, RPMUpgradePhaseTag


class RemoveLeftoverPackages(Actor):
"""
Remove packages left on the system after the upgrade to higher major version of RHEL.
Removal of packages is necessary in order to keep the machine in supported state.
Actor generates report telling users what packages have been removed.
"""

name = 'remove_leftover_packages'
consumes = (LeftoverPackages, )
produces = (Report, RemovedPackages)
tags = (RPMUpgradePhaseTag, IPUWorkflowTag, ExperimentalTag)

def process(self):
removeleftoverpackages.process()
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from leapp.libraries.common.rhsm import skip_rhsm
from leapp.libraries.common.rpms import get_installed_rpms
from leapp.libraries.stdlib import api, CalledProcessError, run
from leapp.models import LeftoverPackages, RemovedPackages, RPM


def get_leftover_packages():
leftover_packages = next(api.consume(LeftoverPackages), LeftoverPackages())
if not leftover_packages.items:
api.current_logger().info('No leftover packages, skipping...')
return None
return leftover_packages


def _get_removed_packages(installed_rpms):
return list(set(installed_rpms) - set(get_installed_rpms()))


def get_removed_packages(installed_rpms):
removed_packages = RemovedPackages()
removed = _get_removed_packages(installed_rpms)

for pkg in removed:
name, version, release, epoch, packager, arch, pgpsig = pkg.split('|')
removed_packages.items.append(RPM(
name=name,
version=version,
epoch=epoch,
packager=packager,
arch=arch,
release=release,
pgpsig=pgpsig
))
return removed_packages


def process():
leftover_packages = get_leftover_packages()
if not leftover_packages:
return

installed_rpms = get_installed_rpms()

to_remove = ['-'.join([pkg.name, pkg.version, pkg.release]) for pkg in leftover_packages.items]
cmd = ['dnf', 'remove', '-y', '--noautoremove'] + to_remove
if skip_rhsm():
# ensure we don't use suscription-manager when it should be skipped
cmd += ['--disableplugin', 'subscription-manager']
try:
run(cmd)
except (CalledProcessError, OSError):
error = 'Failed to remove packages: {}'.format(', '.join(to_remove))
api.current_logger().error(error)
return

api.produce(get_removed_packages(installed_rpms))
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import pytest

from leapp.libraries.actor import removeleftoverpackages
from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked, produce_mocked
from leapp.libraries.stdlib import api, CalledProcessError
from leapp.models import LeftoverPackages, RemovedPackages, RPM


def test_get_leftover_packages(monkeypatch):
rpm = RPM(name='rpm', version='1.0', release='1.el7', epoch='0', packager='foo', arch='noarch', pgpsig='SIG')
monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[LeftoverPackages(items=[rpm])]))
monkeypatch.setattr(api, 'current_logger', logger_mocked())

assert removeleftoverpackages.get_leftover_packages() == LeftoverPackages(items=[rpm])


def test_no_leftover_packages(monkeypatch):
monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[LeftoverPackages()]))
monkeypatch.setattr(api, 'current_logger', logger_mocked())

removeleftoverpackages.get_leftover_packages()

assert api.current_logger.infomsg == ['No leftover packages, skipping...']


def test_remove_lefover_packages_error(monkeypatch):
def get_leftover_pkgs():
return LeftoverPackages(items=[RPM(name='rpm', version='1.0', release='1.el7', epoch='0',
packager='packager', arch='noarch', pgpsig='SIG')])

def mocked_run(cmd):
raise CalledProcessError(command=cmd,
message='mocked error',
result={'stdout': 'out', 'stderr': 'err', 'exit_code': 1, 'signal': 0})

monkeypatch.setattr(api, 'current_logger', logger_mocked())
monkeypatch.setattr(api, 'produce', produce_mocked())
monkeypatch.setattr(removeleftoverpackages, 'get_leftover_packages', get_leftover_pkgs)
monkeypatch.setattr(removeleftoverpackages, 'get_installed_rpms', lambda: [])
monkeypatch.setattr(removeleftoverpackages, 'skip_rhsm', lambda: False)
monkeypatch.setattr(removeleftoverpackages, 'run', mocked_run)

removeleftoverpackages.process()

assert api.produce.called == 0
assert api.current_logger.errmsg == ['Failed to remove packages: rpm-1.0-1.el7']


def test__get_removed_packages(monkeypatch):
pkg1 = 'rpm|1.0|1.el7|0|packager|noarch|SIG'
pkg2 = 'rpm2|1.0|1.el7|0|packager|noarch|SIG'
installed_packages = [pkg1, pkg2]
monkeypatch.setattr(removeleftoverpackages, 'get_installed_rpms', lambda: [pkg1])

assert removeleftoverpackages._get_removed_packages(installed_packages) == [pkg2]


@pytest.mark.parametrize(
('installed_rpms'),
(
([]),
(['rpm1']),
(['rpm1', 'rpm2']),
)
)
def test_get_removed_packages(monkeypatch, installed_rpms):
rpm_details = {
'version': '1.0',
'release': '1.el7',
'epoch': '0',
'packager': 'packager',
'arch': 'noarch',
'pgpsig': 'SIG'
}
rpm_details_composed = '|'.join([rpm_details[key] for key in ['version', 'release', 'epoch',
'packager', 'arch', 'pgpsig']])
mocked_installed_rpms = ['{}|{}'.format(rpm, rpm_details_composed) for rpm in installed_rpms]

monkeypatch.setattr(removeleftoverpackages, '_get_removed_packages', lambda _: mocked_installed_rpms)

removed_packages = removeleftoverpackages.get_removed_packages(mocked_installed_rpms)
expected_output = [RPM(name=rpm, **rpm_details) for rpm in installed_rpms]

assert removed_packages == RemovedPackages(items=expected_output)


@pytest.mark.parametrize(
('removed_packages'),
(
([]),
(['rpm1']),
(['rpm1', 'rpm2']),
)
)
def test_process(monkeypatch, removed_packages):
def get_leftover_pkgs():
return LeftoverPackages()

removed_pkgs = RemovedPackages(items=[RPM(name=pkg, version='1.0', release='1.el7', epoch='0',
packager='packager', arch='noarch', pgpsig='SIG')
for pkg in removed_packages])

def mocked_get_removed_packages(installed_rpms):
return removed_pkgs

monkeypatch.setattr(api, 'produce', produce_mocked())
monkeypatch.setattr(removeleftoverpackages, 'get_leftover_packages', get_leftover_pkgs)
monkeypatch.setattr(removeleftoverpackages, 'get_installed_rpms', lambda: [])
monkeypatch.setattr(removeleftoverpackages, 'run', lambda _: None)
monkeypatch.setattr(removeleftoverpackages, 'skip_rhsm', lambda: False)
monkeypatch.setattr(removeleftoverpackages, 'get_removed_packages', mocked_get_removed_packages)

removeleftoverpackages.process()

assert api.produce.called == 1
assert api.produce.model_instances == [removed_pkgs]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from leapp.actors import Actor
from leapp.libraries.actor import reportleftoverpackages
from leapp.models import LeftoverPackages, RemovedPackages
from leapp.reporting import Report
from leapp.tags import IPUWorkflowTag, RPMUpgradePhaseTag


class ReportLeftoverPackages(Actor):
"""
Collect messages about leftover RHEL packages from older major versions and generate report for users.
Depending on execution of previous actors,
generated report contains information that there are still old RHEL packages
present on the system, which makes it unsupported or lists packages that have been removed.
"""

name = 'report_leftover_packages'
consumes = (LeftoverPackages, RemovedPackages)
produces = (Report,)
tags = (RPMUpgradePhaseTag, IPUWorkflowTag)

def process(self):
reportleftoverpackages.process()
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from leapp import reporting
from leapp.libraries.stdlib import api
from leapp.models import LeftoverPackages, RemovedPackages


def process():
removed_packages = next(api.consume(RemovedPackages), None)
leftover_packages = next(api.consume(LeftoverPackages), LeftoverPackages())
to_remove = ['-'.join([pkg.name, pkg.version, pkg.release]) for pkg in leftover_packages.items]

if removed_packages:
title = 'Leftover RHEL packages have been removed'

if removed_packages.items:
removed = ['-'.join([pkg.name, pkg.version, pkg.release]) for pkg in removed_packages.items]
reporting.create_report([
reporting.Title(title),
reporting.Summary('Following packages have been removed:\n{}'.format('\n'.join(removed))),
reporting.Severity(reporting.Severity.HIGH),
reporting.Groups([reporting.Groups.SANITY]),
] + [reporting.RelatedResource('package', pkg.name) for pkg in removed_packages.items])
else:
summary = ('Following packages have been removed:\n'
'{}\n'
'Dependent packages may have been removed as well, please check that you are not missing '
'any packages.\n'.format('\n'.join(to_remove)))

reporting.create_report([
reporting.Title(title),
reporting.Summary(summary),
reporting.Severity(reporting.Severity.HIGH),
reporting.Groups([reporting.Groups.SANITY]),
] + [reporting.RelatedResource('package', pkg.name) for pkg in leftover_packages.items])
return

if not leftover_packages.items:
api.current_logger().info('No leftover packages, skipping...')
return

summary = 'Following RHEL packages have not been upgraded:\n{}\n'.format('\n'.join(to_remove))
summary += 'Please remove these packages to keep your system in supported state.\n'
reporting.create_report([
reporting.Title('Some RHEL packages have not been upgraded'),
reporting.Summary(summary),
reporting.Severity(reporting.Severity.HIGH),
reporting.Groups([reporting.Groups.SANITY]),
] + [reporting.RelatedResource('package', pkg.name) for pkg in leftover_packages.items])
Loading

0 comments on commit b584b7f

Please sign in to comment.