10
10
11
11
OVERLAY_DO_NOT_MOUNT = ('tmpfs' , 'devpts' , 'sysfs' , 'proc' , 'cramfs' , 'sysv' , 'vfat' )
12
12
13
+ # NOTE(pstodulk): what about using more closer values and than just multiply
14
+ # the final result by magical constant?... this number is most likely going to
15
+ # be lowered and affected by XFS vs EXT4 FSs that needs different spaces each
16
+ # of them.
17
+ _MAGICAL_CONSTANT_OVL_SIZE = 128
18
+ """
19
+ Average size of created disk space images.
20
+
21
+ The size can be lower or higher - usually lower. The value is higher as we want
22
+ to rather prevent future actions in advance instead of resolving later issues
23
+ with the missing space.
24
+ """
25
+
26
+ _MAGICAL_CONSTANT_MIN_CONSUMED_SPACE = 2500
27
+ """
28
+ Average space consumed on downloads and target userspace container installation.
29
+
30
+ On minimal systems it's approx 1.7GiB. Using higher value little bit to cover
31
+ most expected cases. The value can be enlarged in future, but currently seems
32
+ to me as a good compromise.
33
+ """
34
+
13
35
14
36
MountPoints = namedtuple ('MountPoints' , ['fs_file' , 'fs_vfstype' ])
15
37
16
38
39
+ def get_recommended_leapp_free_space (userspace_path = '' ):
40
+ """
41
+ Return recommended free space on partition hosting target userspace container in MB
42
+
43
+ If the path to the container is set, the returned value is updated to
44
+ reflect already consumed space.
45
+
46
+ TODO(pstodulk): this is so far the best trade off between stay safe and do
47
+ do not consume too much space
48
+ """
49
+ return _MAGICAL_CONSTANT_MIN_CONSUMED_SPACE
50
+
51
+ def _ensure_enough_diskimage_space (space_needed , directory ):
52
+ # TODO: not changed yet
53
+ stat = os .statvfs (directory )
54
+ if (stat .f_frsize * stat .f_bavail ) < (space_needed * 1024 * 1024 ):
55
+ # TODO(pstodulk): update the msg? People would be still thinking about
56
+ # LEAPP_OVL_SIZE envar that is obsoleted.
57
+ message = ('Not enough space available for creating required disk images in {directory}. ' +
58
+ 'Needed: {space_needed} MiB' ).format (space_needed = space_needed , directory = directory )
59
+ api .current_logger ().error (message )
60
+ raise StopActorExecutionError (message )
61
+
62
+
17
63
def _get_mountpoints (storage_info ):
64
+ # TODO: not changed yet
18
65
mount_points = set ()
19
66
for entry in storage_info .fstab :
20
67
if os .path .isdir (entry .fs_file ) and entry .fs_vfstype not in OVERLAY_DO_NOT_MOUNT :
21
68
mount_points .add (MountPoints (entry .fs_file , entry .fs_vfstype ))
22
69
elif os .path .isdir (entry .fs_file ) and entry .fs_vfstype == 'vfat' :
70
+ # VFAT FS is not supported to be used for any system partition,
71
+ # so we can safely ignore it
23
72
api .current_logger ().warning (
24
73
'Ignoring vfat {} filesystem mount during upgrade process' .format (entry .fs_file )
25
74
)
@@ -35,6 +84,29 @@ def _mount_dir(mounts_dir, mountpoint):
35
84
return os .path .join (mounts_dir , _mount_name (mountpoint ))
36
85
37
86
87
+ def _prepare_required_mounts (scratch_dir , mounts_dir , storage_info , scratch_reserve ):
88
+ mount_points = _get_mountpoints (storage_info )
89
+ # TODO(pstodulk): update the calculation for bind mounted mount_points (skip)
90
+ space_needed = scratch_reserve + _MAGICAL_CONSTANT_OVL_SIZE * len (mount_points )
91
+ disk_images_directory = os .path .join (scratch_dir , 'diskimages' )
92
+
93
+ # Ensure we cleanup old disk images before we check for space constraints.
94
+ # NOTE(pstodulk): Could we improve the process so we create imgs & calculate
95
+ # the required disk space just once during each leapp (pre)upgrade run?
96
+ run (['rm' , '-rf' , disk_images_directory ])
97
+ _create_diskimages_dir (scratch_dir , disk_images_directory )
98
+ _ensure_enough_diskimage_space (space_needed , scratch_dir )
99
+
100
+ result = {}
101
+ for mountpoint in mount_points :
102
+ image = _create_mount_disk_image (disk_images_directory , mountpoint .fs_file )
103
+ result [mountpoint .fs_file ] = mounting .LoopMount (
104
+ source = image ,
105
+ target = _mount_dir (mounts_dir , mountpoint .fs_file )
106
+ )
107
+ return result
108
+
109
+
38
110
@contextlib .contextmanager
39
111
def _build_overlay_mount (root_mount , mounts ):
40
112
if not root_mount :
@@ -70,6 +142,106 @@ def cleanup_scratch(scratch_dir, mounts_dir):
70
142
api .current_logger ().debug ('Recursively removed scratch directory %s.' , scratch_dir )
71
143
72
144
145
+ def _format_disk_image_ext4 (diskimage_path ):
146
+ """
147
+ Format the specified disk image with Ext4 filesystem.
148
+
149
+ The formatted file system is optimized for operations we want to do and
150
+ mainly for the space it needs to take for the initialisation. So use 32MiB
151
+ journal (that's enough for us as we do not plan to do too many operations
152
+ inside) for any size of the disk image. Also the lazy
153
+ initialisation is disabled. The formatting will be slower, but it helps
154
+ us to estimate better the needed amount of the space for other actions
155
+ done later.
156
+ """
157
+ api .current_logger ().debug ('Creating ext4 filesystem in disk image at %s' , diskimage_path )
158
+ cmd = [
159
+ '/sbin/mkfs.ext4' ,
160
+ '-J' , 'size=32' ,
161
+ '-E' , 'lazy_itable_init=0,lazy_journal_init=0' ,
162
+ '-F' , diskimage_path
163
+ ]
164
+ try :
165
+ utils .call_with_oserror_handled (cmd = cmd )
166
+ except CalledProcessError as e :
167
+ # FIXME(pstodulk): taken from original, but %s seems to me invalid here
168
+ api .current_logger ().error ('Failed to create ext4 filesystem %s' , exc_info = True )
169
+ raise StopActorExecutionError (
170
+ message = str (e )
171
+ )
172
+
173
+ def _format_disk_image_xfs (diskimage_path ):
174
+ """
175
+ Format the specified disk image with XFS filesystem.
176
+
177
+ Set journal just to 32MiB always as we will not need to do too many operation
178
+ inside, so 32MiB should enough for us.
179
+ """
180
+ api .current_logger ().debug ('Creating XFS filesystem in disk image at %s' , diskimage_path )
181
+ cmd = ['/sbin/mkfs.xfs' , '-l' , 'size=32m' , '-f' , diskimage_path ]
182
+ try :
183
+ utils .call_with_oserror_handled (cmd = cmd )
184
+ except CalledProcessError as e :
185
+ # FIXME(pstodulk): taken from original, but %s seems to me invalid here
186
+ api .current_logger ().error ('Failed to create XFS filesystem %s' , exc_info = True )
187
+ raise StopActorExecutionError (
188
+ message = str (e )
189
+ )
190
+
191
+
192
+ def _create_mount_disk_image (disk_images_directory , path , disk_size ):
193
+ """
194
+ Creates the mount disk image
195
+
196
+ The disk image is represented by a sparse file which apparent size
197
+ corresponds to the free space of a particular partition/volume it
198
+ represents.
199
+
200
+ The created disk image uses is formatted with XFS (default) or Ext4 FS
201
+ and it's supposed to be used for write directories of an overlayfs built
202
+ above it.
203
+ """
204
+ diskimage_path = os .path .join (disk_images_directory , _mount_name (path ))
205
+ # TODO(pstodulk): update the disk_size
206
+
207
+ api .current_logger ().debug ('Attempting to create disk image with size %d MiB at %s' , disk_size , diskimage_path )
208
+ # TODO(pstodulk): not sure the hint is still valid..
209
+ utils .call_with_failure_hint (
210
+ cmd = ['/bin/dd' , 'if=/dev/zero' , 'of={}' .format (diskimage_path ), 'bs=1M' , 'count=0' , 'seek={}' .format (disk_size )],
211
+ hint = 'Please ensure that there is enough diskspace in {}.' .format (diskimage_path )
212
+ )
213
+
214
+ if get_env ('LEAPP_OVL_IMG_FS_EXT4' , '0' ) == '1' :
215
+ # This is alternative to XFS in case we find some issues, to be able
216
+ # to switch simply to Ext4, so we will be able to simple investigate
217
+ # possible issues between overlay <-> XFS if any happens.
218
+ _format_disk_image_ext4 (diskimage_path )
219
+ else :
220
+ _format_disk_image_xfs (diskimage_path )
221
+
222
+ return diskimage_path
223
+
224
+
225
+ def _create_diskimages_dir (scratch_dir , diskimages_dir ):
226
+ """
227
+ Prepares directories for disk images
228
+ """
229
+ api .current_logger ().debug ('Creating disk images directory.' )
230
+ try :
231
+ utils .makedirs (diskimages_dir )
232
+ api .current_logger ().debug ('Done creating disk images directory.' )
233
+ except OSError :
234
+ api .current_logger ().error ('Failed to create disk images directory %s' , diskimages_dir , exc_info = True )
235
+
236
+ # This is an attempt for giving the user a chance to resolve it on their own
237
+ raise StopActorExecutionError (
238
+ message = 'Failed to prepare environment for package download while creating directories.' ,
239
+ details = {
240
+ 'hint' : 'Please ensure that {scratch_dir} is empty and modifiable.' .format (scratch_dir = scratch_dir )
241
+ }
242
+ )
243
+
244
+
73
245
def _create_mounts_dir (scratch_dir , mounts_dir ):
74
246
"""
75
247
Prepares directories for mounts
@@ -102,15 +274,59 @@ def _mount_dnf_cache(overlay_target):
102
274
103
275
104
276
@contextlib .contextmanager
105
- def create_source_overlay (mounts_dir , scratch_dir , xfs_info , storage_info , mount_target = None ):
277
+ def create_source_overlay (mounts_dir , scratch_dir , xfs_info , storage_info , mount_target = None , scratch_reserve = 0 ):
106
278
"""
107
279
Context manager that prepares the source system overlay and yields the mount.
280
+
281
+ The in-place upgrade itself requires to do some changes on the system to be
282
+ able to perform the in-place upgrade itself - or even to be able to evaluate
283
+ if the system is possible to upgrade. However, we do not want to (and must not)
284
+ change the original system until we pass beyond the point of not return.
285
+
286
+ For that purposes we have to create a layer above the real host file system,
287
+ where we can safely perform all operations without affecting the system
288
+ setup, rpm database, etc. Currently overlay (OVL) technology showed it is
289
+ capable to handle our requirements good enough - with some limitations.
290
+
291
+ This function prepares a disk image and an overlay layer for each
292
+ mountpoint configured in /etc/fstab, excluding those with FS type noted
293
+ in the OVERLAY_DO_NOT_MOUNT set. Such prepared OVL images are then composed
294
+ together to reflect the real host filesystem. In the end everything is cleaned.
295
+
296
+ The new solution can be now problematic for system with too many partitions
297
+ and loop devices. For such systems we keep for now the possibility of the
298
+ fallback to an old solution, which has however number of issues that are
299
+ fixed by the new design. To fallback to the old solution, set envar:
300
+ LEAPP_OVL_FALLBACK=1
301
+
302
+ Disk images created for OVL are formatted with XFS by default. In case of
303
+ problems, it's possible to switch to Ext4 FS using:
304
+ LEAPP_OVL_IMG_FS_EXT4=1
305
+
306
+ :param mounts_dir: Absolute path to the directory under which all mounts should happen.
307
+ :type mounts_dir: str
308
+ :param scratch_dir: Absolute path to the directory in which all disk and OVL images are stored.
309
+ :type scratch_dir: str
310
+ :param xfs_info: The XFSPresence message.
311
+ :type xfs_info: leapp.models.XFSPresence
312
+ :param storage_info: The StorageInfo message.
313
+ :type storage_info: leapp.models.StorageInfo
314
+ :param mount_target: Directory to which whole source OVL layer should be bind mounted.
315
+ If None (default), mounting.NullMount is created instead
316
+ :type mount_target: Optional[str]
317
+ :param scratch_reserve: Number of MB that should be extra reserved in a partition hosting the scratch_dir.
318
+ :type scratch_reserve: Optional[int]
319
+ :rtype: mounting.BindMount or mounting.NullMount
108
320
"""
109
321
api .current_logger ().debug ('Creating source overlay in {scratch_dir} with mounts in {mounts_dir}' .format (
110
322
scratch_dir = scratch_dir , mounts_dir = mounts_dir ))
111
323
try :
112
324
_create_mounts_dir (scratch_dir , mounts_dir )
113
- mounts = _prepare_required_mounts_old (scratch_dir , mounts_dir , _get_mountpoints (storage_info ), xfs_info )
325
+ if get_env ('LEAPP_OVL_FALLBACK' , '0' ) != '1' :
326
+ mounts = _prepare_required_mounts (scratch_dir , mounts_dir , storage_info , scratch_reserve )
327
+ else :
328
+ # fallback to the deprecated OVL solution
329
+ mounts = _prepare_required_mounts_old (scratch_dir , mounts_dir , _get_mountpoints (storage_info ), xfs_info )
114
330
with mounts .pop ('/' ) as root_mount :
115
331
with mounting .OverlayMount (name = 'system_overlay' , source = '/' , workdir = root_mount .target ) as root_overlay :
116
332
if mount_target :
0 commit comments