storage/lvm: call lvm
directly, don't use qubes-lvm wrapper
The wrapper doesn't do anything else than translating command parameters, but it's load time is significant (because of python imports mostly). Since we can't use python lvm API from non-root user anyway, lets drop the wrapper and call `lvm` directly (or through sudo when necessary). This makes VM startup much faster - storage preparation is down from over 10s to about 3s. QubesOS/qubes-issues#2256
This commit is contained in:
parent
9197bde76e
commit
0471453773
@ -267,37 +267,27 @@ class ThinPool(qubes.storage.Pool):
|
||||
|
||||
def verify(self, volume):
|
||||
''' Verifies the volume. '''
|
||||
cmd = ['sudo', 'qubes-lvm', 'volumes',
|
||||
self.volume_group + '/' + self.thin_pool]
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
||||
result = p.communicate()[0]
|
||||
for line in result.splitlines():
|
||||
if not line.strip():
|
||||
continue
|
||||
vid, atr = line.strip().split(' ')
|
||||
if vid == volume.vid:
|
||||
return atr[4] == 'a'
|
||||
|
||||
return False
|
||||
try:
|
||||
vol_info = size_cache[volume.vid]
|
||||
return vol_info['attr'][4] == 'a'
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
@property
|
||||
def volumes(self):
|
||||
''' Return a list of volumes managed by this pool '''
|
||||
cmd = ['sudo', 'qubes-lvm', 'volumes',
|
||||
self.volume_group + '/' + self.thin_pool]
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
||||
result = p.communicate()[0]
|
||||
volumes = []
|
||||
for line in result.splitlines():
|
||||
if not line.strip():
|
||||
for vid, vol_info in size_cache.items():
|
||||
if not vid.startswith(self.volume_group + '/'):
|
||||
continue
|
||||
if vol_info['pool_lv'] != self.thin_pool:
|
||||
continue
|
||||
vid, atr = line.strip().split(' ')
|
||||
config = {
|
||||
'pool': self.name,
|
||||
'vid': vid,
|
||||
'name': vid,
|
||||
'volume_group': self.volume_group,
|
||||
'rw': atr[1] == 'w',
|
||||
'rw': vol_info['attr'][1] == 'w',
|
||||
}
|
||||
volumes += [ThinVolume(**config)]
|
||||
return volumes
|
||||
@ -315,10 +305,13 @@ class ThinPool(qubes.storage.Pool):
|
||||
|
||||
|
||||
def init_cache(log=logging.getLogger('qube.storage.lvm')):
|
||||
cmd = ['sudo', 'lvs', '--noheadings', '-o',
|
||||
'vg_name,name,lv_size,data_percent', '--units', 'b', '--separator',
|
||||
',']
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
cmd = ['lvs', '--noheadings', '-o',
|
||||
'vg_name,pool_lv,name,lv_size,data_percent,lv_attr',
|
||||
'--units', 'b', '--separator', ',']
|
||||
if os.getuid() != 0:
|
||||
cmd.insert(0, 'sudo')
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
close_fds=True)
|
||||
out, err = p.communicate()
|
||||
return_code = p.returncode
|
||||
if return_code == 0 and err:
|
||||
@ -330,13 +323,14 @@ def init_cache(log=logging.getLogger('qube.storage.lvm')):
|
||||
|
||||
for line in out.splitlines():
|
||||
line = line.strip()
|
||||
pool_name, name, size, usage_percent = line.split(',', 3)
|
||||
if '' in [pool_name, name, size, usage_percent]:
|
||||
pool_name, pool_lv, name, size, usage_percent, attr = line.split(',', 5)
|
||||
if '' in [pool_name, pool_lv, name, size, usage_percent]:
|
||||
continue
|
||||
name = pool_name + "/" + name
|
||||
size = int(size[:-1])
|
||||
usage = int(size / 100 * float(usage_percent))
|
||||
result[name] = {'size':size, 'usage': usage}
|
||||
result[name] = {'size': size, 'usage': usage, 'pool_lv': pool_lv,
|
||||
'attr': attr}
|
||||
|
||||
return result
|
||||
|
||||
@ -416,16 +410,34 @@ class ThinVolume(qubes.storage.Volume):
|
||||
|
||||
def pool_exists(pool_id):
|
||||
''' Return true if pool exists '''
|
||||
cmd = ['pool', pool_id]
|
||||
return qubes_lvm(cmd)
|
||||
try:
|
||||
vol_info = size_cache[pool_id]
|
||||
return vol_info['attr'][0] == 't'
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
|
||||
def qubes_lvm(cmd, log=logging.getLogger('qube.storage.lvm')):
|
||||
''' Call :program:`qubes-lvm` to execute an LVM operation '''
|
||||
# TODO Refactor this ones the udev groups gets fixed and we don't need root
|
||||
# for operations on lvm devices
|
||||
cmd = ['sudo', 'qubes-lvm'] + cmd
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
''' Call :program:`lvm` to execute an LVM operation '''
|
||||
action = cmd[0]
|
||||
if action == 'remove':
|
||||
lvm_cmd = ['lvremove', '-f', cmd[1]]
|
||||
elif action == 'clone':
|
||||
lvm_cmd = ['lvcreate', '-kn', '-ay', '-s', cmd[1], '-n', cmd[2]]
|
||||
elif action == 'create':
|
||||
lvm_cmd = ['lvcreate', '-T', cmd[1], '-kn', '-ay', '-n', cmd[2], '-V',
|
||||
str(cmd[3]) + 'B']
|
||||
elif action == 'extend':
|
||||
size = int(cmd[2]) / (1000 * 1000)
|
||||
lvm_cmd = ["lvextend", "-L%s" % size, cmd[1]]
|
||||
else:
|
||||
raise NotImplementedError('unsupported action: ' + action)
|
||||
if os.getuid() != 0:
|
||||
cmd = ['sudo', 'lvm'] + lvm_cmd
|
||||
else:
|
||||
cmd = ['lvm'] + lvm_cmd
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
close_fds=True)
|
||||
out, err = p.communicate()
|
||||
return_code = p.returncode
|
||||
if out:
|
||||
|
@ -1,277 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
#
|
||||
# The Qubes OS Project, http://www.qubes-os.org
|
||||
#
|
||||
# Copyright (C) 2016 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
''' Manage pools and volumes managed by the 'lvm_thin' driver. '''
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import lvm # pylint: disable=import-error
|
||||
|
||||
log = logging.getLogger('qubes.storage.lvm')
|
||||
|
||||
|
||||
def pool_exists(args):
|
||||
""" Check if given name is an lvm thin volume. """
|
||||
# TODO Implement a faster and proper working version pool_exists
|
||||
vg_name, thin_pool_name = args.pool_id.split('/', 1)
|
||||
volume_group = lvm.vgOpen(vg_name)
|
||||
for p in volume_group.listLVs():
|
||||
if p.getAttr()[0] == 't' and p.getName() == thin_pool_name:
|
||||
volume_group.close()
|
||||
return True
|
||||
|
||||
volume_group.close()
|
||||
return False
|
||||
|
||||
|
||||
def volume_exists(volume):
|
||||
""" Check if the given volume exists and is a thin volume """
|
||||
log.debug("Checking if the %s thin volume exists", volume)
|
||||
assert volume is not None
|
||||
vg_name, volume_name = volume.split('/', 1)
|
||||
volume_group = lvm.vgOpen(vg_name)
|
||||
for p in volume_group.listLVs():
|
||||
if p.getAttr()[0] == 'V' and p.getName() == volume_name:
|
||||
volume_group.close()
|
||||
return True
|
||||
|
||||
volume_group.close()
|
||||
return False
|
||||
|
||||
|
||||
def remove_volume(args):
|
||||
""" Tries to remove the specified logical volume.
|
||||
|
||||
If the removal fails it will try up to 3 times waiting 1, 2 and 3
|
||||
seconds between tries. Most of the time this function fails if some
|
||||
process still has the volume locked.
|
||||
"""
|
||||
img = args.name
|
||||
if not volume_exists(img):
|
||||
log.info("Expected to remove %s, but volume does not exist", img)
|
||||
return
|
||||
|
||||
tries = 1
|
||||
successful = False
|
||||
cmd = ['lvremove', '-f', img]
|
||||
|
||||
while tries <= 3 and not successful:
|
||||
log.info("Trying to remove LVM %s", img)
|
||||
try:
|
||||
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
|
||||
log.debug(output)
|
||||
successful = True
|
||||
except subprocess.CalledProcessError:
|
||||
successful = False
|
||||
|
||||
if successful:
|
||||
break
|
||||
else:
|
||||
time.sleep(tries)
|
||||
tries += 1
|
||||
|
||||
if not successful:
|
||||
log.error('Could not remove volume ' + img)
|
||||
|
||||
|
||||
def clone_volume(args):
|
||||
""" Calls lvcreate and creates new snapshot. """
|
||||
old = args.source
|
||||
new_name = args.destination
|
||||
cmd = ["lvcreate", "-kn", "-ay", "-s", old, "-n", new_name]
|
||||
return subprocess.call(cmd)
|
||||
|
||||
|
||||
def new_volume(args):
|
||||
''' Creates a new volume in the specified thin pool, formated with ext4 '''
|
||||
|
||||
thin_pool = args.pool_id
|
||||
name = args.name
|
||||
size = args.size
|
||||
log.info('Creating new Thin LVM %s in %s VG %s bytes', name, thin_pool,
|
||||
size)
|
||||
cmd = ['lvcreate', '-T', thin_pool, '-kn', '-ay', '-n', name, '-V',
|
||||
str(size) + 'B']
|
||||
|
||||
return subprocess.call(cmd)
|
||||
|
||||
|
||||
def rename_volume(old_name, new_name):
|
||||
''' Rename volume '''
|
||||
log.debug("Renaming LVM %s to %s ", old_name, new_name)
|
||||
retcode = subprocess.call(["lvrename", old_name, new_name])
|
||||
if retcode != 0:
|
||||
raise IOError("Error renaming LVM %s to %s " % (old_name, new_name))
|
||||
return new_name
|
||||
|
||||
|
||||
def extend_volume(args):
|
||||
''' Extends an existing lvm volume. Note this works on any lvm volume not
|
||||
only on thin volumes.
|
||||
'''
|
||||
vid = args.name
|
||||
size = int(args.size) / (1000 * 1000)
|
||||
log.debug("Extending LVM %s to %s", vid, size)
|
||||
cmd = ["lvextend", "-L%s" % size, vid]
|
||||
log.debug(cmd)
|
||||
retcode = subprocess.call(cmd)
|
||||
if retcode != 0:
|
||||
raise IOError("Error extending LVM %s to %s " % (vid, size))
|
||||
return 0
|
||||
|
||||
|
||||
def init_pool_parser(sub_parsers):
|
||||
''' Initialize pool subparser '''
|
||||
pool_parser = sub_parsers.add_parser(
|
||||
'pool',
|
||||
help="Exit with exit code 0 if pool exists")
|
||||
pool_parser.add_argument('pool_id', metavar='VG/POOL',
|
||||
help="volume_group/pool_name")
|
||||
pool_parser.set_defaults(func=pool_exists)
|
||||
|
||||
|
||||
def init_new_parser(sub_parsers):
|
||||
''' Initialize the 'new' subparser '''
|
||||
new_parser = sub_parsers.add_parser(
|
||||
'create',
|
||||
help='Creates a new thin ThinPoolLogicalVolume')
|
||||
new_parser.add_argument('pool_id', metavar='VG/POOL',
|
||||
help="volume_group/pool_name")
|
||||
|
||||
new_parser.add_argument('name',
|
||||
help='name of the new ThinPoolLogicalVolume')
|
||||
new_parser.add_argument(
|
||||
'size', help='size in bytes of the new ThinPoolLogicalVolume')
|
||||
|
||||
new_parser.set_defaults(func=new_volume)
|
||||
|
||||
|
||||
def init_import_parser(sub_parsers):
|
||||
''' Initialize import subparser '''
|
||||
import_parser = sub_parsers.add_parser(
|
||||
'import',
|
||||
help='sparse copy data from stdin to a thin volume')
|
||||
import_parser.add_argument('name', metavar='VG/VID',
|
||||
help='volume_group/volume_name')
|
||||
import_parser.set_defaults(func=import_volume)
|
||||
|
||||
def init_clone_parser(sub_parsers):
|
||||
''' Initialize clone subparser '''
|
||||
clone_parser = sub_parsers.add_parser(
|
||||
'clone',
|
||||
help='sparse copy data from stdin to a thin volume')
|
||||
clone_parser.add_argument('source', metavar='VG/VID',
|
||||
help='volume_group/volume_name')
|
||||
clone_parser.add_argument('destination', metavar='VG/VID',
|
||||
help='volume_group/volume_name')
|
||||
clone_parser.set_defaults(func=clone_volume)
|
||||
|
||||
def import_volume(args):
|
||||
''' Imports from stdin to a thin volume '''
|
||||
name = args.name
|
||||
src = sys.stdin
|
||||
blk_size = 4096
|
||||
zeros = '\x00' * blk_size
|
||||
dst_path = '/dev/%s' % name
|
||||
with open(dst_path, 'wb') as dst:
|
||||
while True:
|
||||
tmp = src.read(blk_size)
|
||||
if not tmp:
|
||||
break
|
||||
elif tmp == zeros:
|
||||
dst.seek(blk_size, 1)
|
||||
else:
|
||||
dst.write(tmp)
|
||||
|
||||
|
||||
def list_volumes(args):
|
||||
''' lists volumes '''
|
||||
vg_name, _ = args.name.split('/')
|
||||
volume_group = lvm.vgOpen(vg_name)
|
||||
for p in volume_group.listLVs():
|
||||
if p.getAttr()[0] == 'V':
|
||||
print(vg_name + "/" + p.getName() + ' ' + p.getAttr())
|
||||
volume_group.close()
|
||||
|
||||
|
||||
def init_volumes_parser(sub_parsers):
|
||||
''' Initialize volumes subparser '''
|
||||
parser = sub_parsers.add_parser('volumes',
|
||||
help='list volumes in a pool')
|
||||
parser.add_argument('name', metavar='VG/THIN_POOL',
|
||||
help='volume_group/thin_pool_name')
|
||||
parser.set_defaults(func=list_volumes)
|
||||
|
||||
|
||||
def init_remove_parser(sub_parsers):
|
||||
''' Initialize remove subparser '''
|
||||
remove_parser = sub_parsers.add_parser('remove',
|
||||
help='Removes a LogicalVolume')
|
||||
remove_parser.add_argument('name', metavar='VG/VID',
|
||||
help='volume_group/volume_name')
|
||||
remove_parser.set_defaults(func=remove_volume)
|
||||
|
||||
|
||||
def init_extend_parser(sub_parsers):
|
||||
''' Initialize extend subparser '''
|
||||
extend_parser = sub_parsers.add_parser('extend',
|
||||
help='extends a LogicalVolume')
|
||||
extend_parser.add_argument('name', metavar='VG/VID',
|
||||
help='volume_group/volume_name')
|
||||
extend_parser.set_defaults(func=extend_volume)
|
||||
extend_parser.add_argument(
|
||||
'size', help='size in bytes of the new ThinPoolLogicalVolume')
|
||||
|
||||
|
||||
def get_parser():
|
||||
'''Create :py:class:`argparse.ArgumentParser` suitable for
|
||||
:program:`qubes-lvm`.
|
||||
'''
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
# pylint: disable=protected-access
|
||||
parser.register('action', 'parsers', argparse._SubParsersAction)
|
||||
sub_parsers = parser.add_subparsers(
|
||||
title='commands',
|
||||
description="For more information see qubes-lvm command -h",
|
||||
dest='command')
|
||||
init_clone_parser(sub_parsers)
|
||||
init_extend_parser(sub_parsers)
|
||||
init_import_parser(sub_parsers)
|
||||
init_new_parser(sub_parsers)
|
||||
init_pool_parser(sub_parsers)
|
||||
init_remove_parser(sub_parsers)
|
||||
init_volumes_parser(sub_parsers)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def main(args=None):
|
||||
'''Main routine of :program:`qubes-lvm`.'''
|
||||
args = get_parser().parse_args(args)
|
||||
return args.func(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
@ -250,7 +250,6 @@ fi
|
||||
%{python_sitelib}/qubes/tools/qubes_monitor_layout_notify.py*
|
||||
%{python_sitelib}/qubes/tools/qubes_prefs.py*
|
||||
%{python_sitelib}/qubes/tools/qvm_block.py*
|
||||
%{python_sitelib}/qubes/tools/qubes_lvm.py*
|
||||
%{python_sitelib}/qubes/tools/qvm_backup.py*
|
||||
%{python_sitelib}/qubes/tools/qvm_backup_restore.py*
|
||||
%{python_sitelib}/qubes/tools/qvm_create.py*
|
||||
|
Loading…
Reference in New Issue
Block a user