-
Notifications
You must be signed in to change notification settings - Fork 144
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Detect Azure hybrid (BIOS/EFI) image and convert /boot/grub2/grubenv symling pointing to /boot/efi/EFI/redhat/grubenv to a normal file as GRUB cannot read the symlink if it is pointing to different partition (in case of BIOS). Consequently, we do not need the "KernelCmdlineArg" with "root_delay" anymore.
- Loading branch information
Showing
11 changed files
with
262 additions
and
5 deletions.
There are no files selected for viewing
23 changes: 23 additions & 0 deletions
23
repos/system_upgrade/el7toel8/actors/cloud/checkhybridimage/actor.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from leapp.actors import Actor | ||
from leapp.models import InstalledRPM, HybridImage, FirmwareFacts | ||
from leapp.tags import ChecksPhaseTag, IPUWorkflowTag | ||
from leapp.libraries.actor.checkhybridimage import check_hybrid_image | ||
|
||
|
||
class CheckHybridImage(Actor): | ||
""" | ||
Check if the system is using Azure hybrid image. | ||
These images have a default relative symlink to EFI | ||
partion even when booted using BIOS and in such cases | ||
GRUB is not able find "grubenv" to get the kernel cmdline | ||
options and fails to boot after upgrade`. | ||
""" | ||
|
||
name = 'checkhybridimage' | ||
consumes = (InstalledRPM, FirmwareFacts) | ||
produces = (HybridImage,) | ||
tags = (ChecksPhaseTag, IPUWorkflowTag) | ||
|
||
def process(self): | ||
check_hybrid_image() |
52 changes: 52 additions & 0 deletions
52
repos/system_upgrade/el7toel8/actors/cloud/checkhybridimage/libraries/checkhybridimage.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import os | ||
|
||
from leapp.libraries.common.rpms import has_package | ||
from leapp.libraries.stdlib import api | ||
from leapp.models import InstalledRPM, HybridImage, FirmwareFacts | ||
from leapp import reporting | ||
from leapp.libraries.common import rhui | ||
|
||
|
||
BIOS_PATH = '/boot/grub2/grubenv' | ||
EFI_PATH = '/boot/efi/EFI/redhat/grubenv' | ||
|
||
|
||
def is_grubenv_symlink_to_efi(): | ||
""" | ||
Check whether '/boot/grub2/grubenv' is a relative symlink to | ||
'/boot/efi/EFI/redhat/grubenv'. | ||
""" | ||
return os.path.islink(BIOS_PATH) and os.path.realpath(BIOS_PATH) == os.path.realpath(EFI_PATH) | ||
|
||
|
||
def is_azure_agent_installed(): | ||
"""Check whether 'WALinuxAgent' package is installed.""" | ||
arch = api.current_actor().configuration.architecture | ||
agent_pkg = rhui.RHUI_CLOUD_MAP[arch]['azure']['agent_pkg'] | ||
return has_package(InstalledRPM, agent_pkg) | ||
|
||
|
||
def is_bios(): | ||
"""Check whether system is booted into BIOS""" | ||
ff = next(api.consume(FirmwareFacts), None) | ||
return ff and ff.firmware == 'bios' | ||
|
||
|
||
def check_hybrid_image(): | ||
"""Check whether the system is using Azure hybrid image.""" | ||
if all([is_grubenv_symlink_to_efi(), is_azure_agent_installed(), is_bios()]): | ||
api.produce(HybridImage(detected=True)) | ||
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.Tags([reporting.Tags.PUBLIC_CLOUD]), | ||
]) |
83 changes: 83 additions & 0 deletions
83
repos/system_upgrade/el7toel8/actors/cloud/checkhybridimage/tests/test_checkhybridimage.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import pytest | ||
|
||
from leapp.libraries.actor import checkhybridimage | ||
from leapp.libraries.common.testutils import produce_mocked, create_report_mocked, CurrentActorMocked | ||
from leapp.libraries.stdlib import api | ||
from leapp.models import FirmwareFacts, InstalledRPM, RPM | ||
from leapp.reporting import Report | ||
from leapp import reporting | ||
|
||
|
||
RH_PACKAGER = 'Red Hat, Inc. <http://bugzilla.redhat.com/bugzilla>' | ||
WA_AGENT_RPM = RPM( | ||
name='WALinuxAgent', version='0.1', release='1.sm01', epoch='1', packager=RH_PACKAGER, arch='noarch', | ||
pgpsig='RSA/SHA256, Mon 01 Jan 1970 00:00:00 AM -03, Key ID 199e2f91fd431d51' | ||
) | ||
NO_AGENT_RPM = RPM( | ||
name='NoAgent', version='0.1', release='1.sm01', epoch='1', packager=RH_PACKAGER, arch='noarch', | ||
pgpsig='RSA/SHA256, Mon 01 Jan 1970 00:00:00 AM -03, Key ID 199e2f91fd431d51' | ||
) | ||
|
||
INSTALLED_AGENT = InstalledRPM(items=[WA_AGENT_RPM]) | ||
NOT_INSTALLED_AGENT = InstalledRPM(items=[NO_AGENT_RPM]) | ||
|
||
BIOS_FIRMWARE = FirmwareFacts(firmware='bios') | ||
EFI_FIRMWARE = FirmwareFacts(firmware='efi') | ||
|
||
BIOS_PATH = '/boot/grub2/grubenv' | ||
EFI_PATH = '/boot/efi/EFI/redhat/grubenv' | ||
|
||
|
||
def test_hybrid_image(monkeypatch, tmpdir): | ||
grubenv_efi = tmpdir.join('grubenv_efi') | ||
grubenv_efi.write('grubenv') | ||
|
||
grubenv_boot = tmpdir.join('grubenv_boot') | ||
grubenv_boot.mksymlinkto('grubenv_efi') | ||
|
||
monkeypatch.setattr(checkhybridimage, 'BIOS_PATH', grubenv_boot.strpath) | ||
monkeypatch.setattr(checkhybridimage, 'EFI_PATH', grubenv_efi.strpath) | ||
monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) | ||
monkeypatch.setattr( | ||
api, 'current_actor', CurrentActorMocked(arch='x86_64', msgs=[BIOS_FIRMWARE, INSTALLED_AGENT]) | ||
) | ||
monkeypatch.setattr(api, "produce", produce_mocked()) | ||
|
||
checkhybridimage.check_hybrid_image() | ||
assert reporting.create_report.called == 1 | ||
assert 'hybrid' in reporting.create_report.report_fields['title'] | ||
assert api.produce.called == 1 | ||
|
||
|
||
@pytest.mark.parametrize('is_symlink, realpath_match, is_bios, agent_installed', [ | ||
(False, True, True, True), | ||
(True, False, True, True), | ||
(True, True, False, True), | ||
(True, True, True, False), | ||
]) | ||
def test_no_hybrid_image(monkeypatch, is_symlink, realpath_match, is_bios, agent_installed, tmpdir): | ||
grubenv_efi = tmpdir.join('grubenv_efi') | ||
grubenv_efi.write('grubenv') | ||
grubenv_efi_false = tmpdir.join('grubenv_efi_false') | ||
grubenv_efi.write('nope') | ||
grubenv_boot = tmpdir.join('grubenv_boot') | ||
|
||
grubenv_target = grubenv_efi if realpath_match else grubenv_efi_false | ||
|
||
if is_symlink: | ||
grubenv_boot.mksymlinkto(grubenv_target) | ||
|
||
firmw = BIOS_FIRMWARE if is_bios else EFI_FIRMWARE | ||
inst_rpms = INSTALLED_AGENT if agent_installed else NOT_INSTALLED_AGENT | ||
|
||
monkeypatch.setattr(checkhybridimage, 'BIOS_PATH', grubenv_boot.strpath) | ||
monkeypatch.setattr(checkhybridimage, 'EFI_PATH', grubenv_efi.strpath) | ||
monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) | ||
monkeypatch.setattr( | ||
api, 'current_actor', CurrentActorMocked(arch='x86_64', msgs=[firmw, inst_rpms]) | ||
) | ||
monkeypatch.setattr(api, "produce", produce_mocked()) | ||
|
||
checkhybridimage.check_hybrid_image() | ||
assert not reporting.create_report.called | ||
assert not api.produce.called |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
28 changes: 28 additions & 0 deletions
28
repos/system_upgrade/el7toel8/actors/cloud/grubenvtofile/actor.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
from leapp.actors import Actor | ||
from leapp.models import HybridImage | ||
from leapp.tags import FinalizationPhaseTag, IPUWorkflowTag | ||
from leapp.libraries.actor.grubenvtofile import grubenv_to_file | ||
|
||
|
||
class GrubenvToFile(Actor): | ||
""" | ||
Convert "grubenv" symlink to a regular file 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. This actor converts the symlink to the 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 = 'grubenvtofile' | ||
consumes = (HybridImage,) | ||
produces = () | ||
tags = (FinalizationPhaseTag, IPUWorkflowTag) | ||
|
||
def process(self): | ||
grubenv_msg = next(self.consume(HybridImage), None) | ||
|
||
if grubenv_msg and grubenv_msg.detected: | ||
grubenv_to_file() |
20 changes: 20 additions & 0 deletions
20
repos/system_upgrade/el7toel8/actors/cloud/grubenvtofile/libraries/grubenvtofile.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from leapp.libraries.stdlib import api, run, CalledProcessError | ||
|
||
|
||
BIOS_PATH = '/boot/grub2/grubenv' | ||
EFI_PATH = '/boot/efi/EFI/redhat/grubenv' | ||
|
||
|
||
def grubenv_to_file(): | ||
try: | ||
run(['unlink', BIOS_PATH]) | ||
except CalledProcessError as err: | ||
api.current_logger().warning('Could not unlink {}: {}'.format(BIOS_PATH, str(err))) | ||
return | ||
try: | ||
run(['cp', '-a', EFI_PATH, BIOS_PATH]) | ||
api.current_logger().info( | ||
'{} converted from being a symlink pointing to {} file into a regular file'.format(BIOS_PATH, EFI_PATH) | ||
) | ||
except CalledProcessError as err: | ||
api.current_logger().warning('Could not copy content of {} to {}: {}'.format(EFI_PATH, BIOS_PATH, str(err))) |
43 changes: 43 additions & 0 deletions
43
repos/system_upgrade/el7toel8/actors/cloud/grubenvtofile/tests/test_grubenvtofile.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import pytest | ||
|
||
from leapp.models import HybridImage | ||
from leapp.libraries.common.testutils import logger_mocked | ||
from leapp.libraries.actor import grubenvtofile | ||
from leapp.libraries.stdlib import CalledProcessError, api | ||
|
||
|
||
def raise_call_error(args=None): | ||
raise CalledProcessError( | ||
message='A Leapp Command Error occured.', | ||
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, 'consume', lambda x: iter([HybridImage()])) | ||
monkeypatch.setattr(grubenvtofile, 'run', run_mocked()) | ||
grubenvtofile.grubenv_to_file() | ||
assert grubenvtofile.run.called == 2 | ||
|
||
|
||
def test_fail_grubenv_to_file(monkeypatch): | ||
monkeypatch.setattr(api, 'consume', lambda x: iter([HybridImage()])) | ||
monkeypatch.setattr(grubenvtofile, 'run', run_mocked(raise_err=True)) | ||
monkeypatch.setattr(api, 'current_logger', logger_mocked()) | ||
grubenvtofile.grubenv_to_file() | ||
assert grubenvtofile.run.called == 1 | ||
assert api.current_logger.warnmsg[0].startswith('Could not unlink') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from leapp.models import Model, fields | ||
from leapp.topics import SystemFactsTopic | ||
|
||
|
||
class HybridImage(Model): | ||
""" | ||
Model used for instructing Leapp to convert "grubenv" symlink | ||
into a regular file in case of hybrid (BIOS/EFI) images using BIOS | ||
on Azure. | ||
""" | ||
topic = SystemFactsTopic | ||
detected = fields.Boolean(default=False) |