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

Feat: New grub2_editenv_list parser #3481

Merged
merged 13 commits into from
Aug 18, 2022
134 changes: 96 additions & 38 deletions insights/parsers/grubenv.py
Original file line number Diff line number Diff line change
@@ -1,76 +1,60 @@
"""
GrubEnv - file ``/boot/grub2/grubenv``
======================================
GRUB environment block
======================
This module provides processing for the GRUB environment block.
Parsers included are:


This parser reads the GRUB environment block file. The file is laid out in a
key=value format similar to an ini file except it doesn't have any headers.
GrubEnv - file ``/boot/grub2/grubenv``
--------------------------------------

The parser stores the key/value pairs in itself which inherits a dict. This
file is only used in Grub 2, but in RHEL8 with BLS being the default. There
were several variables added that are referenced in the
``/boot/loader/entries/*.conf`` files.
Grub2EditenvList - command ``grub2-editenv list``
-------------------------------------------------
"""
from insights import get_active_lines, parser, Parser
from insights.parsers import SkipException
from insights.specs import Specs


@parser(Specs.grubenv)
class GrubEnv(Parser, dict):
class GrubenvBase(Parser, dict):
"""
Parses the /boot/grub2/grubenv file and returns a dict
of the grubenv variables.

Sample output of the file::
This module parses the content of GRUB environment block file -
``/boot/grub2/grubenv``. The file is laid out in a key=value format similar
to an ini file except it doesn't have any headers.

saved_entry=295e1ba1696e4fad9e062f096f92d147-4.18.0-305.el8.x86_64
kernelopts=root=/dev/mapper/root_vg-lv_root ro crashkernel=auto resume=/dev/mapper/root_vg-lv_swap rd.lvm.lv=root_vg/lv_root rd.lvm.lv=root_vg/lv_swap console=tty0 console=ttyS0,115200
boot_success=0
boot_indeterminate=2
tuned_params=transparent_hugepages=never
tuned_initrd=
The module stores the key/value pairs in itself which inherits a dict. This
file is only used in Grub 2, but in RHEL8 with BLS being the default. There
were several variables added that are referenced in the
``/boot/loader/entries/*.conf`` files.

Attributes:
has_kernelopts (bool): Returns True/False depending on if kernelopts key is in the dict.
kernelopts (bool): Returns the string of kernelopts from the dict.
has_tuned_params (str): Returns True/False depending of if the tuned_params key is in the dict.
tuned_params (str): Returns the string of tuned_params from the dict.

Examples:
>>> type(grubenv)
<class 'insights.parsers.grubenv.GrubEnv'>
>>> grubenv.has_kernelopts
True
>>> grubenv.kernelopts
'root=/dev/mapper/root_vg-lv_root ro crashkernel=auto resume=/dev/mapper/root_vg-lv_swap rd.lvm.lv=root_vg/lv_root rd.lvm.lv=root_vg/lv_swap console=tty0 console=ttyS0,115200'
>>> grubenv.has_tuned_params
True
>>> grubenv.tuned_params
'transparent_hugepages=never'
>>> grubenv['saved_entry']
'295e1ba1696e4fad9e062f096f92d147-4.18.0-305.el8.x86_64'
"""

def __init__(self, context):
super(GrubEnv, self).__init__(context)
super(GrubenvBase, self).__init__(context)

def parse_content(self, content):
if not content:
raise SkipException("Empty output.")

self._errors = []
data = dict()
for line in get_active_lines(content):
if "=" not in line:
self._errors.append(line)
continue

key, value = line.split("=", 1)

# Some keys can have empty values, so just skip them.
if not value:
continue

data[key] = value

if not data:
if (not data) and (not self._errors):
raise SkipException("No parsed data.")

self.update(data)
Expand All @@ -90,3 +74,77 @@ def has_tuned_params(self):
@property
def tuned_params(self):
return self.get("tuned_params", "")


@parser(Specs.grubenv)
class GrubEnv(GrubenvBase):
"""
Parses the ``/boot/grub2/grubenv`` file and returns a dict of the grubenv variables.

Sample output of the file::

saved_entry=295e1ba1696e4fad9e062f096f92d147-4.18.0-305.el8.x86_64
kernelopts=root=/dev/mapper/root_vg-lv_root ro crashkernel=auto resume=/dev/mapper/root_vg-lv_swap rd.lvm.lv=root_vg/lv_root rd.lvm.lv=root_vg/lv_swap console=tty0 console=ttyS0,115200
boot_success=0
boot_indeterminate=2
tuned_params=transparent_hugepages=never
tuned_initrd=

Examples:
>>> type(grubenv)
<class 'insights.parsers.grubenv.GrubEnv'>
>>> grubenv.has_kernelopts
True
>>> grubenv.kernelopts
'root=/dev/mapper/root_vg-lv_root ro crashkernel=auto resume=/dev/mapper/root_vg-lv_swap rd.lvm.lv=root_vg/lv_root rd.lvm.lv=root_vg/lv_swap console=tty0 console=ttyS0,115200'
>>> grubenv.has_tuned_params
True
>>> grubenv.tuned_params
'transparent_hugepages=never'
>>> grubenv['saved_entry']
'295e1ba1696e4fad9e062f096f92d147-4.18.0-305.el8.x86_64'
"""
pass


@parser(Specs.grubenv)
class Grub2EditenvList(GrubenvBase):
"""
This parser parses the output of the command ``grub2-editenv list``, which
list the current variables in GRUB environment block file or error message.

The parser processes the ``grub2-editenv list`` command and returns a dict
of the grubenv variables. The command output without error message is same
as the content in ``/boot/grub2/grubenv``.

Sample output of the command::

saved_entry=295e1ba1696e4fad9e062f096f92d147-4.18.0-305.el8.x86_64
kernelopts=root=/dev/mapper/root_vg-lv_root ro crashkernel=auto resume=/dev/mapper/root_vg-lv_swap rd.lvm.lv=root_vg/lv_root rd.lvm.lv=root_vg/lv_swap console=tty0 console=ttyS0,115200
boot_success=0
boot_indeterminate=2
tuned_params=transparent_hugepages=never
tuned_initrd=

Examples:

>>> type(grub2_editenv_list)
<class 'insights.parsers.grubenv.Grub2EditenvList'>
>>> grub2_editenv_list.has_kernelopts
True
>>> grub2_editenv_list.kernelopts
'root=/dev/mapper/root_vg-lv_root ro crashkernel=auto resume=/dev/mapper/root_vg-lv_swap rd.lvm.lv=root_vg/lv_root rd.lvm.lv=root_vg/lv_swap console=tty0 console=ttyS0,115200'
>>> grub2_editenv_list.has_tuned_params
True
>>> grub2_editenv_list.tuned_params
'transparent_hugepages=never'
>>> grub2_editenv_list['saved_entry']
'295e1ba1696e4fad9e062f096f92d147-4.18.0-305.el8.x86_64'
"""

# For 'GrubEnv' parser, it processes the content in '/boot/grub2/grubenv' file.
# The file saves environment variables for boot time, and will never contain error messages.
# The errors attribute only needed for Grub2EditenvList parser
@property
def errors(self):
return self._errors
1 change: 1 addition & 0 deletions insights/specs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ class Specs(SpecSet):
grub_efi_conf = RegistryPoint()
grub1_config_perms = RegistryPoint()
grub2_cfg = RegistryPoint()
grub2_editenv_list = RegistryPoint()
grub2_efi_cfg = RegistryPoint()
grubby_default_index = RegistryPoint()
grubby_default_kernel = RegistryPoint()
Expand Down
1 change: 1 addition & 0 deletions insights/specs/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ class DefaultSpecs(Specs):
grub_efi_conf = simple_file("/boot/efi/EFI/redhat/grub.conf")
grub1_config_perms = simple_command("/bin/ls -lH /boot/grub/grub.conf") # RHEL6
grub2_cfg = simple_file("/boot/grub2/grub.cfg")
grub2_editenv_list = simple_command("/usr/bin/grub2-editenv list")
grub2_efi_cfg = simple_file("boot/efi/EFI/redhat/grub.cfg")
grubby_default_index = simple_command("/usr/sbin/grubby --default-index") # only RHEL7 and updwards
grubby_default_kernel = simple_command("/sbin/grubby --default-kernel")
Expand Down
72 changes: 71 additions & 1 deletion insights/tests/parsers/test_grubenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,38 @@
""".strip() # noqa


GRUBEDITENVLIST_WITH_TUNED_PARAMS = """
saved_entry=295e1ba1696e4fad9e062f096f92d147-4.18.0-305.el8.x86_64
kernelopts=root=/dev/mapper/root_vg-lv_root ro crashkernel=auto resume=/dev/mapper/root_vg-lv_swap rd.lvm.lv=root_vg/lv_root rd.lvm.lv=root_vg/lv_swap console=tty0 console=ttyS0,115200
boot_success=0
boot_indeterminate=2
tuned_params=transparent_hugepages=never
tuned_initrd=
""".strip()

GRUBEDITENVLIST_WITHOUT_TUNED_PARAMS = """
saved_entry=295e1ba1696e4fad9e062f096f92d147-4.18.0-305.el8.x86_64
kernelopts=root=/dev/mapper/root_vg-lv_root ro crashkernel=auto resume=/dev/mapper/root_vg-lv_swap rd.lvm.lv=root_vg/lv_root rd.lvm.lv=root_vg/lv_swap console=tty0 console=ttyS0,115200
boot_success=0
boot_indeterminate=2
""".strip()

GRUBEDITENVLIST_RHEL7 = """
saved_entry=Red Hat Enterprise Linux Server (3.10.0-1127.el7.x86_64) 7.8 (Maipo)
""".strip()

GRUBEDITENVLIST_ERROR = """
grub2-editenv: error: invalid environment block.
""".strip()

GRUBEDITENVLIST_EMPTY = """
"""


def test_doc_examples():
env = {
'grubenv': grubenv.GrubEnv(context_wrap(GRUBENV_WITH_TUNED_PARAMS))
'grubenv': grubenv.GrubEnv(context_wrap(GRUBENV_WITH_TUNED_PARAMS)),
'grub2_editenv_list': grubenv.Grub2EditenvList(context_wrap(GRUBEDITENVLIST_WITH_TUNED_PARAMS))
}
failed, total = doctest.testmod(grubenv, globs=env)
assert failed == 0
Expand Down Expand Up @@ -77,3 +106,44 @@ def test_r7():
def test_skip():
skip_exception_check(grubenv.GrubEnv, output_str="# test")
skip_exception_check(grubenv.GrubEnv)


def test_grub2_editenv_list_with_tuned_params():
results = grubenv.Grub2EditenvList(context_wrap(GRUBEDITENVLIST_WITH_TUNED_PARAMS))
assert results is not None
assert results.has_kernelopts
assert results.has_tuned_params
assert results.kernelopts == "root=/dev/mapper/root_vg-lv_root ro crashkernel=auto resume=/dev/mapper/root_vg-lv_swap rd.lvm.lv=root_vg/lv_root rd.lvm.lv=root_vg/lv_swap console=tty0 console=ttyS0,115200"
assert results.tuned_params == "transparent_hugepages=never"
assert results['boot_success'] == "0"


def test_grub2_editenv_list_with_error():
results = grubenv.Grub2EditenvList(context_wrap(GRUBEDITENVLIST_ERROR))
assert results is not None
assert results.errors == ["grub2-editenv: error: invalid environment block."]


def test_grub2_editenv_list_without_tuned_params():
results = grubenv.Grub2EditenvList(context_wrap(GRUBEDITENVLIST_WITHOUT_TUNED_PARAMS))
assert results is not None
assert results.has_kernelopts
assert not results.has_tuned_params
assert results.kernelopts == "root=/dev/mapper/root_vg-lv_root ro crashkernel=auto resume=/dev/mapper/root_vg-lv_swap rd.lvm.lv=root_vg/lv_root rd.lvm.lv=root_vg/lv_swap console=tty0 console=ttyS0,115200"
assert results.tuned_params == ""
assert results['boot_indeterminate'] == "2"


def test_grub2_editenv_list_rhel7():
results = grubenv.Grub2EditenvList(context_wrap(GRUBEDITENVLIST_RHEL7))
assert results is not None
assert not results.has_kernelopts
assert not results.has_tuned_params
assert results.kernelopts == ""
assert results.tuned_params == ""
assert results['saved_entry'] == "Red Hat Enterprise Linux Server (3.10.0-1127.el7.x86_64) 7.8 (Maipo)"


def test_grub2_editenv_list_skip():
skip_exception_check(grubenv.Grub2EditenvList, output_str="# test")
skip_exception_check(grubenv.Grub2EditenvList)