Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve boot issues in hybrid azure images during upgrades from RHEL 7 > 8 > 9. #1284

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from leapp.actors import Actor
from leapp.libraries.actor import checkgrubenvtofile
from leapp.models import ConvertGrubenvTask, FirmwareFacts, HybridImageAzure
from leapp.reporting import Report
from leapp.tags import ChecksPhaseTag, IPUWorkflowTag


class CheckGrubenvToFile(Actor):
"""
Check whether grubenv is a symlink on Azure hybrid images using BIOS.

Azure images provided by Red Hat aim for hybrid (BIOS/EFI) functionality,
however, currently GRUB is not able to see the "grubenv" file if it is a
symlink to a different partition (default on EFI with grub2-efi pkg
installed) and fails on BIOS systems.

These images have a default relative symlink to EFI partition even when
booted using BIOS and in such cases GRUB is not able to find "grubenv" and
fails to get the kernel cmdline options resulting in system failing to boot
after upgrade.

The symlink needs to be converted to a normal file with the content of
grubenv on the EFI partition in case the system is using BIOS and running
on the Azure cloud. This action is reported in the preupgrade phase.

"""

name = 'check_grubenv_to_file'
consumes = (FirmwareFacts, HybridImageAzure,)
produces = (ConvertGrubenvTask, Report)
tags = (ChecksPhaseTag, IPUWorkflowTag)

def process(self):
checkgrubenvtofile.process()
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from leapp import reporting
from leapp.libraries.stdlib import api
from leapp.models import ConvertGrubenvTask, FirmwareFacts, HybridImageAzure


def process():
hybrid_image = next(api.consume(HybridImageAzure), None)

if not hybrid_image:
return

if not is_bios() or not hybrid_image.grubenv_is_symlink_to_efi:
return

reporting.create_report([
reporting.Title(
'Azure hybrid (BIOS/EFI) image detected. "grubenv" symlink will be converted to a regular file'
),
reporting.Summary(
'Leapp detected the system is running on Azure cloud, booted using BIOS and '
'the "/boot/grub2/grubenv" file is a symlink to "../efi/EFI/redhat/grubenv". In case of such a '
'hybrid image scenario GRUB is not able to locate "grubenv" as it is a symlink to different '
'partition and fails to boot. If the system needs to be run in EFI mode later, please re-create '
'the relative symlink again.'
),
reporting.Severity(reporting.Severity.HIGH),
reporting.Groups([
reporting.Groups.PUBLIC_CLOUD,
reporting.Groups.BOOT
]),
reporting.RelatedResource('file', '/boot/grub2/grubenv'),
reporting.RelatedResource('file', '/boot/efi/EFI/redhat/grubenv'),
])

api.produce(ConvertGrubenvTask())


def is_bios():
"""
Check whether system is booted into BIOS
"""

ff = next(api.consume(FirmwareFacts), None)
return ff and ff.firmware == 'bios'
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import pytest

from leapp import reporting
from leapp.libraries.actor import checkgrubenvtofile
from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, produce_mocked
from leapp.libraries.stdlib import api
from leapp.models import FirmwareFacts, HybridImageAzure

BIOS_FIRMWARE = FirmwareFacts(firmware='bios')
EFI_FIRMWARE = FirmwareFacts(firmware='efi')


@pytest.mark.parametrize('is_hybrid', [True, False])
@pytest.mark.parametrize('is_bios', [True, False])
@pytest.mark.parametrize('is_symlink', [True, False])
def test_check_grubenv_to_file(monkeypatch, tmpdir, is_hybrid, is_bios, is_symlink):

should_report = all([is_hybrid, is_bios, is_symlink])

monkeypatch.setattr(reporting, 'create_report', create_report_mocked())

firmware = BIOS_FIRMWARE if is_bios else EFI_FIRMWARE
msgs = [firmware] + ([HybridImageAzure(grubenv_is_symlink_to_efi=is_symlink)] if is_hybrid else [])
monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(arch='x86_64', msgs=msgs))
monkeypatch.setattr(api, "produce", produce_mocked())

checkgrubenvtofile.process()

if should_report:
assert reporting.create_report.called == 1
assert 'hybrid' in reporting.create_report.report_fields['title']
assert api.produce.called == 1
else:
assert reporting.create_report.called == 0
assert api.produce.called == 0
24 changes: 0 additions & 24 deletions repos/system_upgrade/common/actors/cloud/checkhybridimage/actor.py

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from leapp.actors import Actor
from leapp.libraries.actor import convertgrubenvtofile
from leapp.models import ConvertGrubenvTask
from leapp.tags import FinalizationPhaseTag, IPUWorkflowTag


class ConvertGrubenvToFile(Actor):
"""
Convert "grubenv" symlink to a regular file on Azure hybrid images using BIOS.

For more information see CheckGrubenvToFile actor.

"""

name = 'convert_grubenv_to_file'
consumes = (ConvertGrubenvTask,)
produces = ()
tags = (FinalizationPhaseTag, IPUWorkflowTag)

def process(self):
convertgrubenvtofile.process()
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
from leapp.libraries.stdlib import api, CalledProcessError, run
from leapp.models import ConvertGrubenvTask

BIOS_PATH = '/boot/grub2/grubenv'
EFI_PATH = '/boot/efi/EFI/redhat/grubenv'


def process():
convert_grubenv_task = next(api.consume(ConvertGrubenvTask), None)

if convert_grubenv_task:
grubenv_to_file()


def grubenv_to_file():
try:
run(['unlink', BIOS_PATH])
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import pytest

from leapp.libraries.actor import convertgrubenvtofile
from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked
from leapp.libraries.stdlib import api, CalledProcessError
from leapp.models import ConvertGrubenvTask


def raise_call_error(args=None):
raise CalledProcessError(
message='A Leapp Command Error occurred.',
command=args,
result={'signal': None, 'exit_code': 1, 'pid': 0, 'stdout': 'fake', 'stderr': 'fake'}
)


class run_mocked(object):
def __init__(self, raise_err=False):
self.called = 0
self.args = []
self.raise_err = raise_err

def __call__(self, *args):
self.called += 1
self.args.append(args)
if self.raise_err:
raise_call_error(args)


def test_grubenv_to_file(monkeypatch):
monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(arch='x86_64', msgs=[ConvertGrubenvTask()]))
monkeypatch.setattr(convertgrubenvtofile, 'run', run_mocked(raise_err=False))
convertgrubenvtofile.process()
assert convertgrubenvtofile.run.called == 2


def test_no_grubenv_to_file(monkeypatch):
monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(arch='x86_64', msgs=[]))
monkeypatch.setattr(convertgrubenvtofile, 'run', run_mocked(raise_err=False))
convertgrubenvtofile.process()
assert convertgrubenvtofile.run.called == 0


def test_fail_grubenv_to_file(monkeypatch):
monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(arch='x86_64', msgs=[ConvertGrubenvTask()]))
monkeypatch.setattr(convertgrubenvtofile, 'run', run_mocked(raise_err=True))
monkeypatch.setattr(api, 'current_logger', logger_mocked())
convertgrubenvtofile.grubenv_to_file()

assert convertgrubenvtofile.run.called == 1
assert api.current_logger.warnmsg[0].startswith('Could not unlink')
Loading