Coverage for nova/privsep/fs.py: 91%
182 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-24 11:16 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-24 11:16 +0000
1# Copyright 2016 Red Hat, Inc
2# Copyright 2017 Rackspace Australia
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
16"""
17Helpers for filesystem related routines.
18"""
20import hashlib
22from oslo_concurrency import processutils
23from oslo_log import log as logging
25import nova.privsep
28LOG = logging.getLogger(__name__)
31@nova.privsep.sys_admin_pctxt.entrypoint
32def mount(fstype, device, mountpoint, options):
33 mount_cmd = ['mount']
34 if fstype:
35 mount_cmd.extend(['-t', fstype])
36 if options is not None:
37 mount_cmd.extend(options)
38 mount_cmd.extend([device, mountpoint])
39 return processutils.execute(*mount_cmd)
42@nova.privsep.sys_admin_pctxt.entrypoint
43def umount(mountpoint):
44 processutils.execute('umount', mountpoint, attempts=3, delay_on_retry=True)
47@nova.privsep.sys_admin_pctxt.entrypoint
48def lvcreate(size, lv, vg, preallocated=None):
49 cmd = ['lvcreate']
50 if not preallocated:
51 cmd.extend(['-L', '%db' % size])
52 else:
53 cmd.extend(['-L', '%db' % preallocated,
54 '--virtualsize', '%db' % size])
55 cmd.extend(['-n', lv, vg])
56 processutils.execute(*cmd, attempts=3)
59@nova.privsep.sys_admin_pctxt.entrypoint
60def vginfo(vg):
61 # NOTE(gibi): We see intermittent faults querying volume groups failing
62 # with error code -11, hence the retry. See bug 1931710
63 return processutils.execute(
64 'vgs', '--noheadings', '--nosuffix',
65 '--separator', '|', '--units', 'b',
66 '-o', 'vg_size,vg_free', vg,
67 attempts=3, delay_on_retry=True,
68 )
71@nova.privsep.sys_admin_pctxt.entrypoint
72def lvlist(vg):
73 return processutils.execute(
74 'lvs', '--noheadings', '-o', 'lv_name', vg,
75 attempts=3, delay_on_retry=True)
78@nova.privsep.sys_admin_pctxt.entrypoint
79def lvinfo(path):
80 return processutils.execute('lvs', '-o', 'vg_all,lv_all',
81 '--separator', '|', path)
84@nova.privsep.sys_admin_pctxt.entrypoint
85def lvremove(path):
86 processutils.execute('lvremove', '-f', path, attempts=3)
89@nova.privsep.sys_admin_pctxt.entrypoint
90def blockdev_size(path):
91 return processutils.execute('blockdev', '--getsize64', path)
94@nova.privsep.sys_admin_pctxt.entrypoint
95def blockdev_flush(path):
96 return processutils.execute('blockdev', '--flushbufs', path)
99@nova.privsep.sys_admin_pctxt.entrypoint
100def clear(path, volume_size, shred=False):
101 cmd = ['shred']
102 if shred:
103 cmd.extend(['-n3'])
104 else:
105 cmd.extend(['-n0', '-z'])
106 cmd.extend(['-s%d' % volume_size, path])
107 processutils.execute(*cmd)
110@nova.privsep.sys_admin_pctxt.entrypoint
111def loopsetup(path):
112 return processutils.execute('losetup', '--find', '--show', path)
115@nova.privsep.sys_admin_pctxt.entrypoint
116def loopremove(device):
117 return processutils.execute('losetup', '--detach', device, attempts=3)
120@nova.privsep.sys_admin_pctxt.entrypoint
121def nbd_connect(device, image):
122 return processutils.execute('qemu-nbd', '-c', device, image)
125@nova.privsep.sys_admin_pctxt.entrypoint
126def nbd_disconnect(device):
127 return processutils.execute('qemu-nbd', '-d', device)
130@nova.privsep.sys_admin_pctxt.entrypoint
131def create_device_maps(device):
132 return processutils.execute('kpartx', '-a', device)
135@nova.privsep.sys_admin_pctxt.entrypoint
136def remove_device_maps(device):
137 return processutils.execute('kpartx', '-d', device)
140@nova.privsep.sys_admin_pctxt.entrypoint
141def e2fsck(image, flags='-fp'):
142 unprivileged_e2fsck(image, flags=flags)
145# NOTE(mikal): this method is deliberately not wrapped in a privsep
146# entrypoint. This is not for unit testing, there are some callers who do
147# not require elevated permissions when calling this.
148def unprivileged_e2fsck(image, flags='-fp'):
149 processutils.execute('e2fsck', flags, image, check_exit_code=[0, 1, 2])
152@nova.privsep.sys_admin_pctxt.entrypoint
153def resize2fs(image, check_exit_code, size=None):
154 unprivileged_resize2fs(image, check_exit_code=check_exit_code, size=size)
157# NOTE(mikal): this method is deliberately not wrapped in a privsep
158# entrypoint. This is not for unit testing, there are some callers who do
159# not require elevated permissions when calling this.
160def unprivileged_resize2fs(image, check_exit_code, size=None):
161 if size:
162 cmd = ['resize2fs', image, size]
163 else:
164 cmd = ['resize2fs', image]
166 processutils.execute(*cmd, check_exit_code=check_exit_code)
169@nova.privsep.sys_admin_pctxt.entrypoint
170def create_partition_table(device, style, check_exit_code=True):
171 processutils.execute('parted', '--script', device, 'mklabel', style,
172 check_exit_code=check_exit_code)
175@nova.privsep.sys_admin_pctxt.entrypoint
176def create_partition(device, style, start, end, check_exit_code=True):
177 processutils.execute('parted', '--script', device, '--',
178 'mkpart', style, start, end,
179 check_exit_code=check_exit_code)
182@nova.privsep.sys_admin_pctxt.entrypoint
183def list_partitions(device):
184 return unprivileged_list_partitions(device)
187# NOTE(mikal): this method is deliberately not wrapped in a privsep
188# entrypoint. This is not for unit testing, there are some callers who do
189# not require elevated permissions when calling this.
190def unprivileged_list_partitions(device):
191 """Return partition information (num, size, type) for a device."""
193 out, _err = processutils.execute('parted', '--script', '--machine',
194 device, 'unit s', 'print')
195 lines = [line for line in out.split('\n') if line]
196 partitions = []
198 LOG.debug('Partitions:')
199 for line in lines[2:]:
200 line = line.rstrip(';')
201 num, start, end, size, fstype, name, flags = line.split(':')
202 num = int(num)
203 start = int(start.rstrip('s'))
204 end = int(end.rstrip('s'))
205 size = int(size.rstrip('s'))
206 LOG.debug(' %(num)s: %(fstype)s %(size)d sectors',
207 {'num': num, 'fstype': fstype, 'size': size})
208 partitions.append((num, start, size, fstype, name, flags))
210 return partitions
213@nova.privsep.sys_admin_pctxt.entrypoint
214def resize_partition(device, start, end, bootable):
215 processutils.execute('parted', '--script', device, 'rm', '1')
216 processutils.execute('parted', '--script', device, 'mkpart',
217 'primary', '%ds' % start, '%ds' % end)
218 if bootable: 218 ↛ exitline 218 didn't return from function 'resize_partition' because the condition on line 218 was always true
219 processutils.execute('parted', '--script', device,
220 'set', '1', 'boot', 'on')
223@nova.privsep.sys_admin_pctxt.entrypoint
224def ext_journal_disable(device):
225 processutils.execute('tune2fs', '-O ^has_journal', device)
228@nova.privsep.sys_admin_pctxt.entrypoint
229def ext_journal_enable(device):
230 processutils.execute('tune2fs', '-j', device)
233# NOTE(mikal): nova allows deployers to configure the command line which is
234# used to create a filesystem of a given type. This is frankly a little bit
235# weird, but its also historical and probably should be in some sort of
236# museum. So, we do that thing here, but it requires a funny dance in order
237# to load that configuration at startup.
239# NOTE(mikal): I really feel like this whole thing should be deprecated, I
240# just don't think its a great idea to let people specify a command in a
241# configuration option to run as root.
243_MKFS_COMMAND = {}
244_DEFAULT_MKFS_COMMAND = None
246FS_FORMAT_EXT2 = "ext2"
247FS_FORMAT_EXT3 = "ext3"
248FS_FORMAT_EXT4 = "ext4"
249FS_FORMAT_XFS = "xfs"
250FS_FORMAT_NTFS = "ntfs"
251FS_FORMAT_VFAT = "vfat"
253SUPPORTED_FS_TO_EXTEND = (
254 FS_FORMAT_EXT2,
255 FS_FORMAT_EXT3,
256 FS_FORMAT_EXT4)
258_DEFAULT_FILE_SYSTEM = FS_FORMAT_VFAT
259_DEFAULT_FS_BY_OSTYPE = {'linux': FS_FORMAT_EXT4,
260 'windows': FS_FORMAT_NTFS}
263def load_mkfs_command(os_type, command):
264 global _MKFS_COMMAND
265 global _DEFAULT_MKFS_COMMAND
267 _MKFS_COMMAND[os_type] = command
268 if os_type == 'default':
269 _DEFAULT_MKFS_COMMAND = command
272def get_fs_type_for_os_type(os_type):
273 global _MKFS_COMMAND
275 return os_type if _MKFS_COMMAND.get(os_type) else 'default'
278# NOTE(mikal): this method needs to be duplicated from utils because privsep
279# can't depend on code outside the privsep directory.
280def _get_hash_str(base_str):
281 """Returns string that represents MD5 hash of base_str (in hex format).
283 If base_str is a Unicode string, encode it to UTF-8.
284 """
285 if isinstance(base_str, str): 285 ↛ 287line 285 didn't jump to line 287 because the condition on line 285 was always true
286 base_str = base_str.encode('utf-8')
287 return hashlib.md5(base_str, usedforsecurity=False).hexdigest()
290def get_file_extension_for_os_type(os_type, default_ephemeral_format,
291 specified_fs=None):
292 global _MKFS_COMMAND
293 global _DEFAULT_MKFS_COMMAND
295 mkfs_command = _MKFS_COMMAND.get(os_type, _DEFAULT_MKFS_COMMAND)
296 if mkfs_command:
297 extension = mkfs_command
298 else:
299 if not specified_fs: 299 ↛ 304line 299 didn't jump to line 304 because the condition on line 299 was always true
300 specified_fs = default_ephemeral_format
301 if not specified_fs:
302 specified_fs = _DEFAULT_FS_BY_OSTYPE.get(os_type,
303 _DEFAULT_FILE_SYSTEM)
304 extension = specified_fs
305 return _get_hash_str(extension)[:7]
308@nova.privsep.sys_admin_pctxt.entrypoint
309def mkfs(fs, path, label=None):
310 unprivileged_mkfs(fs, path, label=None)
313# NOTE(mikal): this method is deliberately not wrapped in a privsep
314# entrypoint. This is not for unit testing, there are some callers who do
315# not require elevated permissions when calling this.
316def unprivileged_mkfs(fs, path, label=None):
317 """Format a file or block device
319 :param fs: Filesystem type (examples include 'swap', 'ext3', 'ext4'
320 'btrfs', etc.)
321 :param path: Path to file or block device to format
322 :param label: Volume label to use
323 """
324 if fs == 'swap':
325 args = ['mkswap']
326 else:
327 args = ['mkfs', '-t', fs]
328 # add -F to force no interactive execute on non-block device.
329 if fs in ('ext3', 'ext4', 'ntfs'):
330 args.extend(['-F'])
331 if label:
332 if fs in ('msdos', 'vfat'):
333 label_opt = '-n'
334 else:
335 label_opt = '-L'
336 args.extend([label_opt, label])
337 args.append(path)
338 processutils.execute(*args)
341@nova.privsep.sys_admin_pctxt.entrypoint
342def _inner_configurable_mkfs(os_type, fs_label, target):
343 mkfs_command = (_MKFS_COMMAND.get(os_type, _DEFAULT_MKFS_COMMAND) or
344 '') % {'fs_label': fs_label, 'target': target}
345 processutils.execute(*mkfs_command.split())
348# NOTE(mikal): this method is deliberately not wrapped in a privsep entrypoint
349def configurable_mkfs(os_type, fs_label, target, run_as_root,
350 default_ephemeral_format, specified_fs=None):
351 # Format a file or block device using a user provided command for each
352 # os type. If user has not provided any configuration, format type will
353 # be used according to a default_ephemeral_format configuration or a
354 # system default.
355 global _MKFS_COMMAND
356 global _DEFAULT_MKFS_COMMAND
358 mkfs_command = (_MKFS_COMMAND.get(os_type, _DEFAULT_MKFS_COMMAND) or
359 '') % {'fs_label': fs_label, 'target': target}
360 if mkfs_command: 360 ↛ 361line 360 didn't jump to line 361 because the condition on line 360 was never true
361 if run_as_root:
362 _inner_configurable_mkfs(os_type, fs_label, target)
363 else:
364 processutils.execute(*mkfs_command.split())
366 else:
367 if not specified_fs:
368 specified_fs = default_ephemeral_format
369 if not specified_fs:
370 specified_fs = _DEFAULT_FS_BY_OSTYPE.get(os_type,
371 _DEFAULT_FILE_SYSTEM)
373 if run_as_root: 373 ↛ 376line 373 didn't jump to line 376 because the condition on line 373 was always true
374 mkfs(specified_fs, target, fs_label)
375 else:
376 unprivileged_mkfs(specified_fs, target, fs_label)