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

Refactoring of grub actors using scanner #1012

Merged
merged 2 commits into from
Feb 9, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Refactor GrubConfigError actors using scanner
Existing actors will be used for reporting purposes as consumers
of GrubConfigError. All scanning will be done in a newly intoduced
ScanGrubConfig actor.

OAMG-8337
  • Loading branch information
fernflower committed Feb 7, 2023
commit 20a7c227060c90d2af7d8911cc38aeebeaa49929
46 changes: 0 additions & 46 deletions repos/system_upgrade/common/actors/detectcorruptedgrubenv/actor.py

This file was deleted.

This file was deleted.

This file was deleted.

87 changes: 87 additions & 0 deletions repos/system_upgrade/common/actors/detectgrubconfigerror/actor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from leapp import reporting
from leapp.actors import Actor
from leapp.models import GrubConfigError
from leapp.reporting import create_report, Report
from leapp.tags import ChecksPhaseTag, IPUWorkflowTag


def _create_grub_error_report(error, title, summary, severity=reporting.Severity.LOW,
remediation=None, is_inhibitor=False):
"""
A helper that produces a specific grub error report
"""
# set default group for a grub error report
groups = [reporting.Groups.BOOT]
# set an inhibitor group
if is_inhibitor:
groups.append(reporting.Groups.INHIBITOR)
report_fields = [reporting.Title(title),
reporting.Summary(summary),
reporting.Severity(severity),
reporting.Groups(groups)]
if remediation:
report_fields.append(remediation)
# add information about grub config files
report_fields.extend([reporting.RelatedResource('file', config_file) for config_file in error.files])
# finally produce a report
create_report(report_fields)


class DetectGrubConfigError(Actor):
"""
Check grub configuration for various errors.

Currently 3 types of errors are detected:
- Syntax error in GRUB_CMDLINE_LINUX value;
- Missing newline at the end of file;
- Grubenv config file has a 1K size and doesn't end with a line feed.

There should be only one message of each error type. If for any reason there are more - only the first error of
each type is reported.
"""

name = 'detect_grub_config_error'
consumes = (GrubConfigError,)
produces = (Report,)
tags = (ChecksPhaseTag, IPUWorkflowTag)

def process(self):
# syntax error in GRUB_CMDLINE_LINUX, recoverable
for error in [err for err in self.consume(GrubConfigError)
if err.error_type == GrubConfigError.ERROR_GRUB_CMDLINE_LINUX_SYNTAX]:
_create_grub_error_report(
MichalHe marked this conversation as resolved.
Show resolved Hide resolved
error=error,
title='Syntax error detected in grub configuration',
summary=('Syntax error was detected in GRUB_CMDLINE_LINUX value of grub configuration. '
'This error is causing booting and other issues. '
'Error is automatically fixed by add_upgrade_boot_entry actor.'),
)
break
# missing newline, recoverable
for error in [err for err in self.consume(GrubConfigError)
if err.error_type == GrubConfigError.ERROR_MISSING_NEWLINE]:
_create_grub_error_report(
error=error,
title='Detected a missing newline at the end of grub configuration file',
summary=('The missing newline in /etc/default/grub causes booting issues when appending '
'new entries to this file during the upgrade. Leapp will automatically fix this '
'problem by appending the missing newline to the grub configuration file.')
)
break
# corrupted configuration, inhibitor
for error in [err for err in self.consume(GrubConfigError)
if err.error_type == GrubConfigError.ERROR_CORRUPTED_GRUBENV]:
_create_grub_error_report(
error=error,
title='Detected a corrupted grubenv file',
summary=('The grubenv file must be valid to pass the upgrade correctly: \n'
'- an exact size of 1024 bytes is expected \n'
'- it cannot end with a newline. \n'
'The corruption could be caused by a manual modification of the file which '
'is not recommended.'),
severity=reporting.Severity.HIGH,
is_inhibitor=True,
remediation=reporting.Remediation(
hint='Delete {} file(s) and regenerate grubenv using the grub2-mkconfig tool'.format(
','.join(error.files))))
break
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from leapp.models import GrubConfigError, Report
from leapp.utils import report

grub_cmdline_syntax_error = GrubConfigError(error_type=GrubConfigError.ERROR_GRUB_CMDLINE_LINUX_SYNTAX,
files=['/etc/default/grub.cfg'])
grub_cmdline_syntax_error2 = GrubConfigError(error_type=GrubConfigError.ERROR_GRUB_CMDLINE_LINUX_SYNTAX,
files=['/boot/grub2/grub.cfg', '/etc/default/someothergrub.cfg'])

grub_missing_newline_error = GrubConfigError(error_type=GrubConfigError.ERROR_MISSING_NEWLINE,
files=['/etc/default/someothergrub.cfg'])
grub_missing_newline_error2 = GrubConfigError(error_type=GrubConfigError.ERROR_MISSING_NEWLINE,
files=['/etc/default/grub'])

grub_corrupted_config = GrubConfigError(error_type=GrubConfigError.ERROR_CORRUPTED_GRUBENV,
files=['/boot/grub2/grub.cfg', '/boot/efi/EFI/redhat/grub.cfg'])
grub_corrupted_config2 = GrubConfigError(error_type=GrubConfigError.ERROR_CORRUPTED_GRUBENV,
files=['/boot/grub2/grub.cfg'])


def test_cmdline_syntax_error(current_actor_context):
# Make sure that just 1 low priority report message is created with config files present.
current_actor_context.feed(grub_cmdline_syntax_error)
current_actor_context.feed(grub_cmdline_syntax_error2)
current_actor_context.run()
messages = current_actor_context.consume(Report)
assert len(messages) == 1
message = messages[0]
assert 'Syntax error detected in grub configuration' in message.report['title']
assert message.report['severity'] == 'low'
assert message.report['detail']['related_resources'][0]['title'] == '/etc/default/grub.cfg'


def test_missing_newline(current_actor_context):
# Make sure that just 1 low priority report message is created with config files present
current_actor_context.feed(grub_missing_newline_error)
current_actor_context.feed(grub_missing_newline_error2)
current_actor_context.run()
messages = current_actor_context.consume(Report)
assert len(messages) == 1
message = messages[0]
assert 'Detected a missing newline at the end of grub configuration file' in message.report['title']
assert message.report['severity'] == 'low'
assert message.report['detail']['related_resources'][0]['title'] == '/etc/default/someothergrub.cfg'


def test_corrupted_config(current_actor_context):
# Make sure that just 1 high priority report message is created with config files present
current_actor_context.feed(grub_corrupted_config)
current_actor_context.feed(grub_corrupted_config2)
current_actor_context.run()
messages = current_actor_context.consume(Report)
assert len(messages) == 1
message = messages[0]
assert 'Detected a corrupted grubenv file' in message.report['title']
assert message.report['severity'] == 'high'
assert message.report['detail']['related_resources'][0]['title'] == '/boot/grub2/grub.cfg'
assert message.report['detail']['related_resources'][1]['title'] == '/boot/efi/EFI/redhat/grub.cfg'
assert report.is_inhibitor(message.report)

This file was deleted.

This file was deleted.

This file was deleted.

21 changes: 21 additions & 0 deletions repos/system_upgrade/common/actors/scangrubconfig/actor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from leapp.actors import Actor
from leapp.libraries.actor import scanner
from leapp.models import GrubConfigError
from leapp.tags import FactsPhaseTag, IPUWorkflowTag


class ScanGrubConfig(Actor):
"""
Scan grub configuration files for errors.
"""

name = 'scan_grub_config'
consumes = ()
produces = (GrubConfigError,)
tags = (FactsPhaseTag, IPUWorkflowTag)

def process(self):
errors = scanner.scan()
if errors:
for error in errors:
self.produce(error)
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import os
import re

from leapp.libraries.common.config import architecture, version
from leapp.models import GrubConfigError


def is_grubenv_corrupted(conf_file):
# grubenv can be missing
if not os.path.exists(conf_file):
return False
# ignore when /boot/grub2/grubenv is a symlink to its EFI counterpart
if os.path.islink(conf_file) and os.readlink(conf_file) == '../efi/EFI/redhat/grubenv':
return False
with open(conf_file, 'r') as config:
config_contents = config.read()
return len(config_contents) != 1024 or config_contents[-1] == '\n'


def _get_config_contents(config_path):
if os.path.isfile(config_path):
with open(config_path, 'r') as config:
return config.read()
return ''


def is_grub_config_missing_final_newline(conf_file):
config_contents = _get_config_contents(conf_file)
return config_contents and config_contents[-1] != '\n'


def detect_config_error(conf_file):
fernflower marked this conversation as resolved.
Show resolved Hide resolved
"""
Check grub configuration for syntax error in GRUB_CMDLINE_LINUX value.

:return: Function returns True if error was detected, otherwise False.
"""
with open(conf_file, 'r') as f:
config = f.read()

pattern = r'GRUB_CMDLINE_LINUX="[^"]+"(?!(\s*$)|(\s+(GRUB|#)))'
return re.search(pattern, config) is not None


def scan():
errors = []
# Check for corrupted grubenv
if not architecture.matches_architecture(architecture.ARCH_S390X):
configs = ['/boot/grub2/grubenv', '/boot/efi/EFI/redhat/grubenv']
corrupted = []
for cfg in configs:
if is_grubenv_corrupted(cfg):
corrupted.append(cfg)
if corrupted:
errors.append(GrubConfigError(error_type=GrubConfigError.ERROR_CORRUPTED_GRUBENV, files=corrupted))

config = '/etc/default/grub'
# Check for GRUB_CMDLINE_LINUX syntax errors
# XXX FIXME(ivasilev) Can we make this check a common one? For now let's limit it to rhel7->rhel8 only
if version.get_source_major_version() == '7':
if not architecture.matches_architecture(architecture.ARCH_S390X):
# For now, skip just s390x, that's only one that is failing now
# because ZIPL is used there
if detect_config_error(config):
errors.append(GrubConfigError(error_detected=True, files=[config],
error_type=GrubConfigError.ERROR_GRUB_CMDLINE_LINUX_SYNTAX))

# Check for missing newline errors
if is_grub_config_missing_final_newline(config):
errors.append(GrubConfigError(error_detected=True, error_type=GrubConfigError.ERROR_MISSING_NEWLINE,
files=[config]))

return errors
Loading