Files
nova/nova/privsep/fs.py
Balazs Gibizer e59fc77c3d Retry lvm volume and volume group query
We observed that the vgs and lvs queries the our lvm driver uses can
intermittently fail with error code 11 (EAGAIN). So this patch enabled
the retry in oslo_concurrency.processutils for these calls.

Change-Id: I93da6cb1675d77bcdcd1075641dea9e2afc0ee1a
Closes-Bug: #1931710
2021-06-15 12:39:26 +02:00

376 lines
12 KiB
Python

# Copyright 2016 Red Hat, Inc
# Copyright 2017 Rackspace Australia
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Helpers for filesystem related routines.
"""
from oslo_concurrency import processutils
from oslo_log import log as logging
from oslo_utils.secretutils import md5
import nova.privsep
LOG = logging.getLogger(__name__)
@nova.privsep.sys_admin_pctxt.entrypoint
def mount(fstype, device, mountpoint, options):
mount_cmd = ['mount']
if fstype:
mount_cmd.extend(['-t', fstype])
if options is not None:
mount_cmd.extend(options)
mount_cmd.extend([device, mountpoint])
return processutils.execute(*mount_cmd)
@nova.privsep.sys_admin_pctxt.entrypoint
def umount(mountpoint):
processutils.execute('umount', mountpoint, attempts=3, delay_on_retry=True)
@nova.privsep.sys_admin_pctxt.entrypoint
def lvcreate(size, lv, vg, preallocated=None):
cmd = ['lvcreate']
if not preallocated:
cmd.extend(['-L', '%db' % size])
else:
cmd.extend(['-L', '%db' % preallocated,
'--virtualsize', '%db' % size])
cmd.extend(['-n', lv, vg])
processutils.execute(*cmd, attempts=3)
@nova.privsep.sys_admin_pctxt.entrypoint
def vginfo(vg):
# NOTE(gibi): We see intermittent faults querying volume groups failing
# with error code -11, hence the retry. See bug 1931710
return processutils.execute(
'vgs', '--noheadings', '--nosuffix',
'--separator', '|', '--units', 'b',
'-o', 'vg_size,vg_free', vg,
attempts=3, delay_on_retry=True,
)
@nova.privsep.sys_admin_pctxt.entrypoint
def lvlist(vg):
return processutils.execute(
'lvs', '--noheadings', '-o', 'lv_name', vg,
attempts=3, delay_on_retry=True)
@nova.privsep.sys_admin_pctxt.entrypoint
def lvinfo(path):
return processutils.execute('lvs', '-o', 'vg_all,lv_all',
'--separator', '|', path)
@nova.privsep.sys_admin_pctxt.entrypoint
def lvremove(path):
processutils.execute('lvremove', '-f', path, attempts=3)
@nova.privsep.sys_admin_pctxt.entrypoint
def blockdev_size(path):
return processutils.execute('blockdev', '--getsize64', path)
@nova.privsep.sys_admin_pctxt.entrypoint
def blockdev_flush(path):
return processutils.execute('blockdev', '--flushbufs', path)
@nova.privsep.sys_admin_pctxt.entrypoint
def clear(path, volume_size, shred=False):
cmd = ['shred']
if shred:
cmd.extend(['-n3'])
else:
cmd.extend(['-n0', '-z'])
cmd.extend(['-s%d' % volume_size, path])
processutils.execute(*cmd)
@nova.privsep.sys_admin_pctxt.entrypoint
def loopsetup(path):
return processutils.execute('losetup', '--find', '--show', path)
@nova.privsep.sys_admin_pctxt.entrypoint
def loopremove(device):
return processutils.execute('losetup', '--detach', device, attempts=3)
@nova.privsep.sys_admin_pctxt.entrypoint
def nbd_connect(device, image):
return processutils.execute('qemu-nbd', '-c', device, image)
@nova.privsep.sys_admin_pctxt.entrypoint
def nbd_disconnect(device):
return processutils.execute('qemu-nbd', '-d', device)
@nova.privsep.sys_admin_pctxt.entrypoint
def create_device_maps(device):
return processutils.execute('kpartx', '-a', device)
@nova.privsep.sys_admin_pctxt.entrypoint
def remove_device_maps(device):
return processutils.execute('kpartx', '-d', device)
@nova.privsep.sys_admin_pctxt.entrypoint
def e2fsck(image, flags='-fp'):
unprivileged_e2fsck(image, flags=flags)
# NOTE(mikal): this method is deliberately not wrapped in a privsep
# entrypoint. This is not for unit testing, there are some callers who do
# not require elevated permissions when calling this.
def unprivileged_e2fsck(image, flags='-fp'):
processutils.execute('e2fsck', flags, image, check_exit_code=[0, 1, 2])
@nova.privsep.sys_admin_pctxt.entrypoint
def resize2fs(image, check_exit_code, size=None):
unprivileged_resize2fs(image, check_exit_code=check_exit_code, size=size)
# NOTE(mikal): this method is deliberately not wrapped in a privsep
# entrypoint. This is not for unit testing, there are some callers who do
# not require elevated permissions when calling this.
def unprivileged_resize2fs(image, check_exit_code, size=None):
if size:
cmd = ['resize2fs', image, size]
else:
cmd = ['resize2fs', image]
processutils.execute(*cmd, check_exit_code=check_exit_code)
@nova.privsep.sys_admin_pctxt.entrypoint
def create_partition_table(device, style, check_exit_code=True):
processutils.execute('parted', '--script', device, 'mklabel', style,
check_exit_code=check_exit_code)
@nova.privsep.sys_admin_pctxt.entrypoint
def create_partition(device, style, start, end, check_exit_code=True):
processutils.execute('parted', '--script', device, '--',
'mkpart', style, start, end,
check_exit_code=check_exit_code)
@nova.privsep.sys_admin_pctxt.entrypoint
def list_partitions(device):
return unprivileged_list_partitions(device)
# NOTE(mikal): this method is deliberately not wrapped in a privsep
# entrypoint. This is not for unit testing, there are some callers who do
# not require elevated permissions when calling this.
def unprivileged_list_partitions(device):
"""Return partition information (num, size, type) for a device."""
out, _err = processutils.execute('parted', '--script', '--machine',
device, 'unit s', 'print')
lines = [line for line in out.split('\n') if line]
partitions = []
LOG.debug('Partitions:')
for line in lines[2:]:
line = line.rstrip(';')
num, start, end, size, fstype, name, flags = line.split(':')
num = int(num)
start = int(start.rstrip('s'))
end = int(end.rstrip('s'))
size = int(size.rstrip('s'))
LOG.debug(' %(num)s: %(fstype)s %(size)d sectors',
{'num': num, 'fstype': fstype, 'size': size})
partitions.append((num, start, size, fstype, name, flags))
return partitions
@nova.privsep.sys_admin_pctxt.entrypoint
def resize_partition(device, start, end, bootable):
processutils.execute('parted', '--script', device, 'rm', '1')
processutils.execute('parted', '--script', device, 'mkpart',
'primary', '%ds' % start, '%ds' % end)
if bootable:
processutils.execute('parted', '--script', device,
'set', '1', 'boot', 'on')
@nova.privsep.sys_admin_pctxt.entrypoint
def ext_journal_disable(device):
processutils.execute('tune2fs', '-O ^has_journal', device)
@nova.privsep.sys_admin_pctxt.entrypoint
def ext_journal_enable(device):
processutils.execute('tune2fs', '-j', device)
# NOTE(mikal): nova allows deployers to configure the command line which is
# used to create a filesystem of a given type. This is frankly a little bit
# weird, but its also historical and probably should be in some sort of
# museum. So, we do that thing here, but it requires a funny dance in order
# to load that configuration at startup.
# NOTE(mikal): I really feel like this whole thing should be deprecated, I
# just don't think its a great idea to let people specify a command in a
# configuration option to run as root.
_MKFS_COMMAND = {}
_DEFAULT_MKFS_COMMAND = None
FS_FORMAT_EXT2 = "ext2"
FS_FORMAT_EXT3 = "ext3"
FS_FORMAT_EXT4 = "ext4"
FS_FORMAT_XFS = "xfs"
FS_FORMAT_NTFS = "ntfs"
FS_FORMAT_VFAT = "vfat"
SUPPORTED_FS_TO_EXTEND = (
FS_FORMAT_EXT2,
FS_FORMAT_EXT3,
FS_FORMAT_EXT4)
_DEFAULT_FILE_SYSTEM = FS_FORMAT_VFAT
_DEFAULT_FS_BY_OSTYPE = {'linux': FS_FORMAT_EXT4,
'windows': FS_FORMAT_NTFS}
def load_mkfs_command(os_type, command):
global _MKFS_COMMAND
global _DEFAULT_MKFS_COMMAND
_MKFS_COMMAND[os_type] = command
if os_type == 'default':
_DEFAULT_MKFS_COMMAND = command
def get_fs_type_for_os_type(os_type):
global _MKFS_COMMAND
return os_type if _MKFS_COMMAND.get(os_type) else 'default'
# NOTE(mikal): this method needs to be duplicated from utils because privsep
# can't depend on code outside the privsep directory.
def _get_hash_str(base_str):
"""Returns string that represents MD5 hash of base_str (in hex format).
If base_str is a Unicode string, encode it to UTF-8.
"""
if isinstance(base_str, str):
base_str = base_str.encode('utf-8')
return md5(base_str, usedforsecurity=False).hexdigest()
def get_file_extension_for_os_type(os_type, default_ephemeral_format,
specified_fs=None):
global _MKFS_COMMAND
global _DEFAULT_MKFS_COMMAND
mkfs_command = _MKFS_COMMAND.get(os_type, _DEFAULT_MKFS_COMMAND)
if mkfs_command:
extension = mkfs_command
else:
if not specified_fs:
specified_fs = default_ephemeral_format
if not specified_fs:
specified_fs = _DEFAULT_FS_BY_OSTYPE.get(os_type,
_DEFAULT_FILE_SYSTEM)
extension = specified_fs
return _get_hash_str(extension)[:7]
@nova.privsep.sys_admin_pctxt.entrypoint
def mkfs(fs, path, label=None):
unprivileged_mkfs(fs, path, label=None)
# NOTE(mikal): this method is deliberately not wrapped in a privsep
# entrypoint. This is not for unit testing, there are some callers who do
# not require elevated permissions when calling this.
def unprivileged_mkfs(fs, path, label=None):
"""Format a file or block device
:param fs: Filesystem type (examples include 'swap', 'ext3', 'ext4'
'btrfs', etc.)
:param path: Path to file or block device to format
:param label: Volume label to use
"""
if fs == 'swap':
args = ['mkswap']
else:
args = ['mkfs', '-t', fs]
# add -F to force no interactive execute on non-block device.
if fs in ('ext3', 'ext4', 'ntfs'):
args.extend(['-F'])
if label:
if fs in ('msdos', 'vfat'):
label_opt = '-n'
else:
label_opt = '-L'
args.extend([label_opt, label])
args.append(path)
processutils.execute(*args)
@nova.privsep.sys_admin_pctxt.entrypoint
def _inner_configurable_mkfs(os_type, fs_label, target):
mkfs_command = (_MKFS_COMMAND.get(os_type, _DEFAULT_MKFS_COMMAND) or
'') % {'fs_label': fs_label, 'target': target}
processutils.execute(*mkfs_command.split())
# NOTE(mikal): this method is deliberately not wrapped in a privsep entrypoint
def configurable_mkfs(os_type, fs_label, target, run_as_root,
default_ephemeral_format, specified_fs=None):
# Format a file or block device using a user provided command for each
# os type. If user has not provided any configuration, format type will
# be used according to a default_ephemeral_format configuration or a
# system default.
global _MKFS_COMMAND
global _DEFAULT_MKFS_COMMAND
mkfs_command = (_MKFS_COMMAND.get(os_type, _DEFAULT_MKFS_COMMAND) or
'') % {'fs_label': fs_label, 'target': target}
if mkfs_command:
if run_as_root:
_inner_configurable_mkfs(os_type, fs_label, target)
else:
processutils.execute(*mkfs_command.split())
else:
if not specified_fs:
specified_fs = default_ephemeral_format
if not specified_fs:
specified_fs = _DEFAULT_FS_BY_OSTYPE.get(os_type,
_DEFAULT_FILE_SYSTEM)
if run_as_root:
mkfs(specified_fs, target, fs_label)
else:
unprivileged_mkfs(specified_fs, target, fs_label)