Merge remote-tracking branch 'qubesos/pr/52' into core3-devel

* qubesos/pr/52:
  qvm-block extend Fix help message
  Fix a few typo bugs in qubes.storage.lvm
  qvm-block extends volumes to NEW_SIZE
  Make pylint happy ♥ qubes.storage.lvm
  Fix ThinVolume _size access
  qvm-block extend use qubes.utils.parse_size
  Make pylint happy ♥ qubes_lvm
  Fix lvm size/usage
  Add ThinPool.resize()
  Document qubes-block extend command in the manpage
  Fix file storage resize
  qvm-block add extend sub command
  qvm-block refactor attach/detach sub_parser init
This commit is contained in:
Marek Marczykowski-Górecki 2016-09-04 21:21:43 +02:00
commit ae72e294cd
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
5 changed files with 162 additions and 19 deletions

View File

@ -86,6 +86,12 @@ Detach the volume with *POOL_NAME:VOLUME_ID* from domain *VMNAME*
aliases: d, dt
extend
^^^^^^
| :command:`qvm-block extend` [-h] [--verbose] [--quiet] *POOL_NAME:VOLUME_ID* *NEW_SIZE*
Extend the volume with *POOL_NAME:VOLUME_ID* TO *NEW_SIZE*
revert
^^^^^^

View File

@ -136,6 +136,7 @@ class FilePool(qubes.storage.Pool):
# resize loop device
subprocess.check_call(['sudo', 'losetup', '--set-capacity',
loop_dev])
volume.size = size
def remove(self, volume):
if not volume.internal:

View File

@ -32,6 +32,8 @@ class ThinPool(qubes.storage.Pool):
''' LVM Thin based pool implementation
''' # pylint: disable=protected-access
size_cache = None
driver = 'lvm_thin'
def __init__(self, volume_group, thin_pool, revisions_to_keep=1, **kwargs):
@ -90,6 +92,7 @@ class ThinPool(qubes.storage.Pool):
str(volume.size)
]
qubes_lvm(cmd, self.log)
reset_cache()
return volume
def destroy(self):
@ -146,6 +149,7 @@ class ThinPool(qubes.storage.Pool):
dst.write(tmp)
p.stdin.close()
p.wait()
reset_cache()
return dst_volume
def is_dirty(self, volume):
@ -161,6 +165,7 @@ class ThinPool(qubes.storage.Pool):
cmd = ['remove', volume.vid]
qubes_lvm(cmd, self.log)
reset_cache()
def rename(self, volume, old_name, new_name):
''' Called when the domain changes its name '''
@ -178,6 +183,7 @@ class ThinPool(qubes.storage.Pool):
if not volume._is_volatile:
volume._vid_snap = volume.vid + '-snap'
reset_cache()
return volume
def revert(self, volume, revision=None):
@ -190,8 +196,29 @@ class ThinPool(qubes.storage.Pool):
qubes_lvm(cmd, self.log)
cmd = ['clone', volume.vid + '-back', volume.vid]
qubes_lvm(cmd, self.log)
reset_cache()
return volume
def resize(self, volume, size):
''' Expands volume, throws
:py:class:`qubst.storage.qubes.storage.StoragePoolException` if
given size is less than current_size
'''
if not volume.rw:
msg = 'Can not resize reađonly volume {!s}'.format(volume)
raise qubes.storage.StoragePoolException(msg)
if size <= volume.size:
raise qubes.storage.StoragePoolException(
'For your own safety, shrinking of %s is'
' disabled. If you really know what you'
' are doing, use `lvresize` on %s manually.' %
(volume.name, volume.vid))
cmd = ['extend', volume.vid, str(size)]
qubes_lvm(cmd, self.log)
reset_cache()
def _reset(self, volume):
try:
self.remove(volume)
@ -212,6 +239,7 @@ class ThinPool(qubes.storage.Pool):
if not self.is_dirty(volume):
self._snapshot(volume)
reset_cache()
return volume
def stop(self, volume):
@ -226,6 +254,7 @@ class ThinPool(qubes.storage.Pool):
else:
cmd = ['remove', volume._vid_snap]
qubes_lvm(cmd, self.log)
reset_cache()
return volume
def _snapshot(self, volume):
@ -289,13 +318,44 @@ class ThinPool(qubes.storage.Pool):
str(volume.size)]
qubes_lvm(cmd, self.log)
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)
out, err = p.communicate()
return_code = p.returncode
if return_code == 0 and err:
log.warning(err)
elif return_code != 0:
raise qubes.storage.StoragePoolException(err)
result = {}
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]:
continue
name = pool_name + "/" + name
size = int(size[:-1])
usage = int(size / 100 * float(usage_percent))
result[name] = {'size':size, 'usage': usage}
return result
size_cache = init_cache()
class ThinVolume(qubes.storage.Volume):
''' Default LVM thin volume implementation
''' # pylint: disable=too-few-public-methods
def __init__(self, volume_group, **kwargs):
def __init__(self, volume_group, size=0, **kwargs):
self.volume_group = volume_group
super(ThinVolume, self).__init__(**kwargs)
super(ThinVolume, self).__init__(size=size, **kwargs)
if self.snap_on_start and self.source is None:
msg = "snap_on_start specified on {!r} but no volume source set"
@ -310,6 +370,8 @@ class ThinVolume(qubes.storage.Volume):
if not self._is_volatile:
self._vid_snap = self.vid + '-snap'
self._size = size
@property
def revisions(self):
path = self.path + '-back'
@ -335,6 +397,22 @@ class ThinVolume(qubes.storage.Volume):
def _is_volatile(self):
return not self.snap_on_start and not self.save_on_stop
@property
def size(self):
try:
return qubes.storage.lvm.size_cache[self.vid]['size']
except KeyError:
return self._size
@property
def usage(self): # lvm thin usage always returns at least the same usage as
# the parent
try:
return qubes.storage.lvm.size_cache[self.vid]['usage']
except KeyError:
return 0
def pool_exists(pool_id):
''' Return true if pool exists '''
cmd = ['pool', pool_id]
@ -357,3 +435,7 @@ def qubes_lvm(cmd, log=logging.getLogger('qube.storage.lvm')):
assert err, "Command exited unsuccessful, but printed nothing to stderr"
raise qubes.storage.StoragePoolException(err)
return True
def reset_cache():
qubes.storage.lvm.size_cache = init_cache

View File

@ -127,6 +127,21 @@ def rename_volume(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(
@ -219,6 +234,17 @@ def init_remove_parser(sub_parsers):
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`.
@ -230,12 +256,13 @@ def get_parser():
title='commands',
description="For more information see qubes-lvm command -h",
dest='command')
init_pool_parser(sub_parsers)
init_clone_parser(sub_parsers)
init_extend_parser(sub_parsers)
init_import_parser(sub_parsers)
init_new_parser(sub_parsers)
init_volumes_parser(sub_parsers)
init_pool_parser(sub_parsers)
init_remove_parser(sub_parsers)
init_clone_parser(sub_parsers)
init_volumes_parser(sub_parsers)
return parser

View File

@ -29,6 +29,7 @@ import sys
import qubes
import qubes.exc
import qubes.tools
import qubes.utils
def prepare_table(vd_list, full=False):
@ -171,6 +172,17 @@ def detach_volumes(args):
sys.exit(1)
def extend_volumes(args):
''' Called by the parser to execute the :program:`qvm-block extend`
subcommand
'''
volume = args.volume
app = args.app
size = qubes.utils.parse_size(args.size)
pool = app.get_pool(volume.pool)
pool.resize(volume, volume.size+size)
app.save()
def init_list_parser(sub_parsers):
''' Configures the parser for the :program:`qvm-block list` subcommand '''
# pylint: disable=protected-access
@ -198,6 +210,32 @@ def init_revert_parser(sub_parsers):
action=qubes.tools.VolumeAction)
revert_parser.set_defaults(func=revert_volume)
def init_attach_parser(sub_parsers):
attach_parser = sub_parsers.add_parser(
'attach', help="Attach volume to domain", aliases=('at', 'a'))
attach_parser.add_argument('--ro', help='attach device read-only',
action='store_true')
attach_parser.add_argument('VMNAME', action=qubes.tools.RunningVmNameAction)
attach_parser.add_argument(metavar='POOL_NAME:VOLUME_ID', dest='volume',
action=qubes.tools.VolumeAction)
attach_parser.set_defaults(func=attach_volumes)
def init_dettach_parser(sub_parsers):
detach_parser = sub_parsers.add_parser(
"detach", help="Detach volume from domain", aliases=('d', 'dt'))
detach_parser.add_argument('VMNAME', action=qubes.tools.RunningVmNameAction)
detach_parser.add_argument(metavar='POOL_NAME:VOLUME_ID', dest='volume',
action=qubes.tools.VolumeAction)
detach_parser.set_defaults(func=detach_volumes)
def init_extend_parser(sub_parsers):
extend_parser = sub_parsers.add_parser(
"extend", help="extend volume from domain", aliases=('d', 'dt'))
extend_parser.add_argument(metavar='POOL_NAME:VOLUME_ID', dest='volume',
action=qubes.tools.VolumeAction)
extend_parser.add_argument('size', help='New size in bytes')
extend_parser.set_defaults(func=extend_volumes)
def get_parser():
'''Create :py:class:`argparse.ArgumentParser` suitable for
@ -209,22 +247,11 @@ def get_parser():
title='commands',
description="For more information see qvm-block command -h",
dest='command')
init_attach_parser(sub_parsers)
init_dettach_parser(sub_parsers)
init_extend_parser(sub_parsers)
init_list_parser(sub_parsers)
init_revert_parser(sub_parsers)
attach_parser = sub_parsers.add_parser(
'attach', help="Attach volume to domain", aliases=('at', 'a'))
attach_parser.add_argument('--ro', help='attach device read-only',
action='store_true')
attach_parser.add_argument('VMNAME', action=qubes.tools.RunningVmNameAction)
attach_parser.add_argument(metavar='POOL_NAME:VOLUME_ID', dest='volume',
action=qubes.tools.VolumeAction)
attach_parser.set_defaults(func=attach_volumes)
detach_parser = sub_parsers.add_parser(
"detach", help="Detach volume from domain", aliases=('d', 'dt'))
detach_parser.add_argument('VMNAME', action=qubes.tools.RunningVmNameAction)
detach_parser.add_argument(metavar='POOL_NAME:VOLUME_ID', dest='volume',
action=qubes.tools.VolumeAction)
detach_parser.set_defaults(func=detach_volumes)
return parser