Skip to content

Commit

Permalink
upgradeinitramfsgenerator: Check the free space prior the initeramfs …
Browse files Browse the repository at this point in the history
…generation

Under rare conditions it's possible the last piece free space
is consumed when the upgrade initramfs is generated. It's hard
to hit this problems right now without additional customisations
that consume more space than we expect. However, when it happens,
it not good situation. From this point, check the remaining free
space on the FS hosting the container. In case we have less than
500MB, do not even try. Possibly we will increase the value in future,
but consider it good enough for now.
  • Loading branch information
pirat89 committed Jul 17, 2023
1 parent 025b970 commit 591cdb8
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

INITRAM_GEN_SCRIPT_NAME = 'generate-initram.sh'
DRACUT_DIR = '/dracut'
DEDICATED_LEAPP_PART_URL = 'https://access.redhat.com/solutions/7011704'


def _get_target_kernel_version(context):
Expand Down Expand Up @@ -231,13 +232,85 @@ def _update_files(copy_files):
_copy_files(context, files)


def _get_fspace(path, convert_to_mibs=False, coefficient=1):
"""
Return the free disk space on given path.
The default is in bytes, but if convert_to_mibs is True, return MiBs instead.
Raises OSError if nothing exists on the given `path`.
:param path: Path to an existing file or directory
:type path: str
:param convert_to_mibs: If True, convert the value to MiBs
:type convert_to_mibs: bool
:param coefficient: Coefficient to multiply the free space (e.g. 0.9 to have it 10% lower). Max: 1
:type coefficient: float
:rtype: int
"""
# TODO(pstodulk): discuss the function params
# NOTE(pstodulk): This func is copied from the overlaygen.py lib
# probably it would make sense to make it public in the utils.py lib,
# but for now, let's keep it private
stat = os.statvfs(path)

coefficient = min(coefficient, 1)
fspace_bytes = int(stat.f_frsize * stat.f_bavail * coefficient)
if convert_to_mibs:
return int(fspace_bytes / 1024 / 1024) # noqa: W1619; pylint: disable=old-division
return fspace_bytes


def _check_free_space(context):
"""
Raise StopActorExecutionError if there is less than 500MB of free space available.
If there is not enough free space in the context, the initramfs will not be
generated successfully and it's hard to discover what was the issue. Also
the missing space is able to kill the leapp itself - trying to write to the
leapp.db when the FS hosting /var/lib/leapp is full, kills the framework
and the actor execution too - so there is no gentle way to handle such
exceptions when it happens. From this point, let's rather check the available
space in advance and stop the execution when it happens.
It is not expected to hit this issue, but I was successful and I know
it's still possible even with all other changes (just it's much harder
now to hit it). So adding this seatbelt, that is not 100% bulletproof,
but I call it good enough.
Currently protecting last 500MB. In case of problems, we can increase
the value.
"""
message = 'There is not enough space on the file system hosting /var/lib/leapp.'
hint = (
'Increase the free space on the filesystem hosting'
' /var/lib/leapp by 500MB at minimum (suggested 1500MB).\n\n'
'It is also a good practice to create dedicated partition'
' for /var/lib/leapp when more space is needed, which can be'
' dropped after the system upgrade is fully completed.'
' For more info, see: {}'
.format(DEDICATED_LEAPP_PART_URL)
)
detail = (
'Remaining free space is lower than 500MB which is not enough to'
' be able to generate the upgrade initramfs. '
)

if _get_fspace(context.base_dir, convert_to_mibs=True) < 500:
raise StopActorExecutionError(
message=message,
details={'hint': hint, 'detail': detail}
)


def generate_initram_disk(context):
"""
Function to actually execute the init ramdisk creation.
Includes handling of specified dracut and kernel modules from the host when
needed. The check for the 'conflicting' modules is in a separate actor.
"""
_check_free_space(context)
env = {}
if get_target_major_version() == '9':
env = {'SYSTEMD_SECCOMP': '0'}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from leapp.utils.deprecation import suppress_deprecation

from leapp.models import ( # isort:skip
FIPSInfo,
RequiredUpgradeInitramPackages, # deprecated
UpgradeDracutModule, # deprecated
BootContent,
Expand Down Expand Up @@ -250,6 +251,16 @@ def test_prepare_userspace_for_initram(monkeypatch, adjust_cwd, input_msgs, pkgs
assert _sort_files(upgradeinitramfsgenerator._copy_files.args[1]) == _files


class MockedGetFspace(object):
def __init__(self, space):
self.space = space

def __call__(self, dummy_path, convert_to_mibs=False):
if not convert_to_mibs:
return self.space
return int(self.space / 1024 / 1024) # noqa: W1619; pylint: disable=old-division


@pytest.mark.parametrize('input_msgs,dracut_modules,kernel_modules', [
# test dracut modules with UpgradeDracutModule(s) - orig functionality
(gen_UDM_list(MODULES[0]), MODULES[0], []),
Expand All @@ -275,8 +286,11 @@ def test_generate_initram_disk(monkeypatch, input_msgs, dracut_modules, kernel_m
monkeypatch.setattr(upgradeinitramfsgenerator, '_get_target_kernel_version', lambda _: '')
monkeypatch.setattr(upgradeinitramfsgenerator, 'copy_kernel_modules', MockedCopyArgs())
monkeypatch.setattr(upgradeinitramfsgenerator, 'copy_boot_files', lambda dummy: None)
monkeypatch.setattr(upgradeinitramfsgenerator, '_get_fspace', MockedGetFspace(2*2**30))
upgradeinitramfsgenerator.generate_initram_disk(context)

# TODO(pstodulk): add tests for the check of the free space (sep. from this func)

# test now just that all modules have been passed for copying - so we know
# all modules have been consumed
detected_dracut_modules = set()
Expand Down

0 comments on commit 591cdb8

Please sign in to comment.