diff --git a/repos/system_upgrade/el7toel8/actors/checklegacygrub/actor.py b/repos/system_upgrade/el7toel8/actors/checklegacygrub/actor.py new file mode 100644 index 0000000000..b035a8750b --- /dev/null +++ b/repos/system_upgrade/el7toel8/actors/checklegacygrub/actor.py @@ -0,0 +1,20 @@ +from leapp.actors import Actor +from leapp.libraries.actor import check_legacy_grub as check_legacy_grub_lib +from leapp.reporting import Report +from leapp.tags import ChecksPhaseTag, IPUWorkflowTag + + +class CheckLegacyGrub(Actor): + """ + Check whether GRUB Legacy is installed in the MBR. + + GRUB Legacy is deprecated since RHEL 7 in favour of GRUB2. + """ + + name = 'check_grub_legacy' + consumes = () + produces = (Report,) + tags = (ChecksPhaseTag, IPUWorkflowTag) + + def process(self): + check_legacy_grub_lib.check_grub_disks_for_legacy_grub() diff --git a/repos/system_upgrade/el7toel8/actors/checklegacygrub/libraries/check_legacy_grub.py b/repos/system_upgrade/el7toel8/actors/checklegacygrub/libraries/check_legacy_grub.py new file mode 100644 index 0000000000..1ce581649b --- /dev/null +++ b/repos/system_upgrade/el7toel8/actors/checklegacygrub/libraries/check_legacy_grub.py @@ -0,0 +1,53 @@ +from leapp import reporting +from leapp.exceptions import StopActorExecution +from leapp.libraries.common import grub as grub_lib +from leapp.libraries.stdlib import api, CalledProcessError, run +from leapp.reporting import create_report + +# There is no grub legacy package on RHEL7, therefore, the system must have been upgraded from RHEL6 +MIGRATION_TO_GRUB2_GUIDE_URL = 'https://access.redhat.com/solutions/2643721' + + +def has_legacy_grub(device): + try: + output = run(['file', '-s', device]) + except CalledProcessError as err: + msg = 'Failed to determine the file type for the special device `{0}`. Full error: `{1}`' + api.current_logger().warning(msg.format(device, str(err))) + + # According to `file` manpage, the exit code > 0 iff the file does not exists (meaning) + # that grub_lib.get_grub_devices() is unreliable for some reason (better stop the upgrade), + # or because the file type could not be determined. However, its manpage directly gives examples + # of file -s being used on block devices, so this should be unlikely - especially if one would + # consider that get_grub_devices was able to determine that it is a grub device. + raise StopActorExecution() + + grub_legacy_version_string = 'GRUB version 0.94' + return grub_legacy_version_string in output['stdout'] + + +def check_grub_disks_for_legacy_grub(): + # Both GRUB2 and Grub Legacy are recognized by `get_grub_devices` + grub_devices = grub_lib.get_grub_devices() + + legacy_grub_devices = [] + for device in grub_devices: + if has_legacy_grub(device): + legacy_grub_devices.append(device) + + if legacy_grub_devices: + details = ('Leapp detected GRUB Legacy to be installed on the system. ' + 'The GRUB Legacy bootloader is unsupported on RHEL7 and GRUB2 should be used instead. ' + 'GRUB Legacy has been detected on following devices:\n' + '{block_devices_fmt}\n') + block_devices_fmt = '\n'.join(legacy_grub_devices) + create_report([ + reporting.Title("GRUB Legacy is used on the system"), + reporting.Summary(details.format(block_devices_fmt=block_devices_fmt)), + reporting.Severity(reporting.Severity.HIGH), + reporting.Groups([reporting.Groups.BOOT]), + reporting.Remediation(hint='Migrate to the GRUB2 bootloader on the reported devices.'), + reporting.Groups([reporting.Groups.INHIBITOR]), + reporting.ExternalLink(url=MIGRATION_TO_GRUB2_GUIDE_URL, + title='How to install GRUB2 after a RHEL6 to RHEL7 upgrade'), + ]) diff --git a/repos/system_upgrade/el7toel8/actors/checklegacygrub/tests/test_check_legacy_grub.py b/repos/system_upgrade/el7toel8/actors/checklegacygrub/tests/test_check_legacy_grub.py new file mode 100644 index 0000000000..d6e5008e5b --- /dev/null +++ b/repos/system_upgrade/el7toel8/actors/checklegacygrub/tests/test_check_legacy_grub.py @@ -0,0 +1,45 @@ +import pytest + +from leapp.libraries.actor import check_legacy_grub as check_legacy_grub_lib +from leapp.libraries.common import grub as grub_lib +from leapp.libraries.common.testutils import create_report_mocked +from leapp.utils.report import is_inhibitor + +VDA_WITH_LEGACY_GRUB = ( + '/dev/vda: x86 boot sector; GRand Unified Bootloader, stage1 version 0x3, ' + 'stage2 address 0x2000, stage2 segment 0x200, GRUB version 0.94; partition 1: ID=0x83, ' + 'active, starthead 32, startsector 2048, 1024000 sectors; partition 2: ID=0x83, starthead 221, ' + 'startsector 1026048, 19945472 sectors, code offset 0x48\n' +) + +NVME0N1_VDB_WITH_GRUB = ( + '/dev/nvme0n1: x86 boot sector; partition 1: ID=0x83, active, starthead 32, startsector 2048, 6291456 sectors; ' + 'partition 2: ID=0x83, starthead 191, startsector 6293504, 993921024 sectors, code offset 0x63' +) + + +@pytest.mark.parametrize( + ('grub_device_to_file_output', 'should_inhibit'), + [ + ({'/dev/vda': VDA_WITH_LEGACY_GRUB}, True), + ({'/dev/nvme0n1': NVME0N1_VDB_WITH_GRUB}, False), + ({'/dev/vda': VDA_WITH_LEGACY_GRUB, '/dev/nvme0n1': NVME0N1_VDB_WITH_GRUB}, True) + ] +) +def test_check_legacy_grub(monkeypatch, grub_device_to_file_output, should_inhibit): + + def file_cmd_mock(cmd, *args, **kwargs): + assert cmd[:2] == ['file', '-s'] + return {'stdout': grub_device_to_file_output[cmd[2]]} + + monkeypatch.setattr(check_legacy_grub_lib, 'create_report', create_report_mocked()) + monkeypatch.setattr(grub_lib, 'get_grub_devices', lambda: list(grub_device_to_file_output.keys())) + monkeypatch.setattr(check_legacy_grub_lib, 'run', file_cmd_mock) + + check_legacy_grub_lib.check_grub_disks_for_legacy_grub() + + assert bool(check_legacy_grub_lib.create_report.called) == should_inhibit + if should_inhibit: + assert len(check_legacy_grub_lib.create_report.reports) == 1 + report = check_legacy_grub_lib.create_report.reports[0] + assert is_inhibitor(report)