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:
commit
ae72e294cd
@ -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
|
||||
^^^^^^
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user