From 7495b2ddef89e7cefbba9ee7888fbcc3d24a0372 Mon Sep 17 00:00:00 2001 From: Michal Hecko Date: Thu, 5 Sep 2024 21:57:08 +0200 Subject: [PATCH] overlaygen: cap max sparse file size If the source system has a very large disk attached, leapp would attempt to create similarly large sparse file, possibly hitting the max file size limits of the filesystem where these images are created. This patch caps the maximum size of any of the created sparse files to 1TB, preventing such issues. Jira ref: RHEL-57064 --- .../common/libraries/overlaygen.py | 23 ++++++++++++ .../common/libraries/tests/test_overlaygen.py | 37 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 repos/system_upgrade/common/libraries/tests/test_overlaygen.py diff --git a/repos/system_upgrade/common/libraries/overlaygen.py b/repos/system_upgrade/common/libraries/overlaygen.py index c1ac9ad3aa..4d59caa3ff 100644 --- a/repos/system_upgrade/common/libraries/overlaygen.py +++ b/repos/system_upgrade/common/libraries/overlaygen.py @@ -68,6 +68,21 @@ this constant. """ +_MAX_DISK_IMAGE_SIZE_MB = 1024**2 # 1*TB +""" +Maximum size of the created (sparse) images. + +Defaults to 1TB. If a disk with capacity larger than _MAX_DISK_IMAGE_SIZE_MB +is mounted on the system, the corresponding image used to store overlay +modifications will be capped to _MAX_DISK_IMAGE_SIZE_MB. + +Engineering rationale: + This constant was introduced to prevent leapp from creating files that are + virtually larger than the maximum file size supported by the file system. + E.g. if the source system hosts /var/lib/leapp on EXT4, then we cannot + create a file larger than 16TB. +""" + MountPoints = namedtuple('MountPoints', ['fs_file', 'fs_vfstype']) @@ -261,6 +276,7 @@ def _prepare_required_mounts(scratch_dir, mounts_dir, storage_info, scratch_rese :type scratch_reserve: Optional[int] """ mount_points = sorted([mp.fs_file for mp in _get_mountpoints(storage_info)]) + import pdb; pdb.set_trace() scratch_mp = _get_scratch_mountpoint(mount_points, scratch_dir) disk_images_directory = os.path.join(scratch_dir, 'diskimages') @@ -287,6 +303,13 @@ def _prepare_required_mounts(scratch_dir, mounts_dir, storage_info, scratch_rese disk_size = _get_fspace(mountpoint, convert_to_mibs=True, coefficient=0.95) if mountpoint == scratch_mp: disk_size = scratch_disk_size + + if disk_size > _MAX_DISK_IMAGE_SIZE_MB: + msg = ('Image for overlayfs corresponding to the disk mounted at %s would ideally have %d MB, ' + 'but we truncate it to %d MB to avoid bumping to max file limits.') + api.current_logger().info(msg, mountpoint, disk_size, _MAX_DISK_IMAGE_SIZE_MB) + disk_size = _MAX_DISK_IMAGE_SIZE_MB + image = _create_mount_disk_image(disk_images_directory, mountpoint, disk_size) result[mountpoint] = mounting.LoopMount( source=image, diff --git a/repos/system_upgrade/common/libraries/tests/test_overlaygen.py b/repos/system_upgrade/common/libraries/tests/test_overlaygen.py new file mode 100644 index 0000000000..964bbe348f --- /dev/null +++ b/repos/system_upgrade/common/libraries/tests/test_overlaygen.py @@ -0,0 +1,37 @@ +from collections import namedtuple + +from leapp.libraries.common import overlaygen + + +class RunMocked(object): + def __init__(self): + self.invocations = [] + + def __call__(self, *args, **kwargs): + self.invocations.append((args, kwargs)) + + +def test_prepare_required_mounts_image_size_capping(monkeypatch): + """ Test whether having a very large disk will not create very large image. """ + MountPoint = namedtuple('MountPoint', 'fs_file') + mount_points = [MountPoint(fs_file='/big_disk')] + monkeypatch.setattr(overlaygen, '_get_mountpoints', lambda storage_info: mount_points) + monkeypatch.setattr(overlaygen, '_get_scratch_mountpoint', lambda *args: '/scratch') + monkeypatch.setattr(overlaygen, '_create_diskimages_dir', lambda *args: None) + monkeypatch.setattr(overlaygen, '_ensure_enough_diskimage_space', lambda *args: None) + + mountpoint_sizes = { + '/big_disk': 20*(1024**2), # 20TB + '/scratch': 20 # Arbitrary, irrelevant for this test + } + monkeypatch.setattr(overlaygen, '_get_fspace', lambda mount, *args, **kwargs: mountpoint_sizes[mount]) + monkeypatch.setattr(overlaygen, 'run', RunMocked()) + + def create_disk_image_mock(disk_images_dir, mountpoint, disk_size): + assert mountpoint == '/big_disk' + assert disk_size == overlaygen._MAX_DISK_IMAGE_SIZE_MB + + monkeypatch.setattr(overlaygen, '_create_mount_disk_image', create_disk_image_mock) + + storage_info = None # Should not be used in the code due to how we mocked overlaygen functions + overlaygen._prepare_required_mounts('/scratch', '/mounts', storage_info, 100)