storage: add size and usage properties to pool object

Add Pool.size and Pool.usage to the API. Implement them for LVM and File
pools. Add appropriate tests.

QubesOS/qubes-issues#3240
This commit is contained in:
Marek Marczykowski-Górecki 2017-10-29 02:23:00 +02:00
parent 682d9503ee
commit e76372b934
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
5 changed files with 73 additions and 1 deletions

View File

@ -815,6 +815,16 @@ class Pool(object):
''' '''
raise self._not_implemented("get_volume") raise self._not_implemented("get_volume")
@property
def size(self):
''' Storage pool size in bytes '''
raise self._not_implemented("size")
@property
def usage(self):
''' Space used in the pool, in bytes '''
raise self._not_implemented("usage")
def _not_implemented(self, method_name): def _not_implemented(self, method_name):
''' Helper for emitting helpful `NotImplementedError` exceptions ''' ''' Helper for emitting helpful `NotImplementedError` exceptions '''
msg = "Pool driver {!s} has {!s}() not implemented" msg = "Pool driver {!s} has {!s}() not implemented"

View File

@ -144,6 +144,16 @@ class FilePool(qubes.storage.Pool):
def list_volumes(self): def list_volumes(self):
return self._volumes return self._volumes
@property
def size(self):
statvfs = os.statvfs(self.dir_path)
return statvfs.f_frsize * statvfs.f_blocks
@property
def usage(self):
statvfs = os.statvfs(self.dir_path)
return statvfs.f_frsize * (statvfs.f_blocks - statvfs.f_bfree)
class FileVolume(qubes.storage.Volume): class FileVolume(qubes.storage.Volume):
''' Parent class for the xen volumes implementation which expects a ''' Parent class for the xen volumes implementation which expects a

View File

@ -137,6 +137,22 @@ class ThinPool(qubes.storage.Pool):
volumes += [ThinVolume(**config)] volumes += [ThinVolume(**config)]
return volumes return volumes
@property
def size(self):
try:
return qubes.storage.lvm.size_cache[
self.volume_group + '/' + self.thin_pool]['size']
except KeyError:
return 0
@property
def usage(self):
try:
return qubes.storage.lvm.size_cache[
self.volume_group + '/' + self.thin_pool]['usage']
except KeyError:
return 0
def init_cache(log=logging.getLogger('qubes.storage.lvm')): def init_cache(log=logging.getLogger('qubes.storage.lvm')):
cmd = ['lvs', '--noheadings', '-o', cmd = ['lvs', '--noheadings', '-o',
@ -159,7 +175,7 @@ def init_cache(log=logging.getLogger('qubes.storage.lvm')):
line = line.decode().strip() line = line.decode().strip()
pool_name, pool_lv, name, size, usage_percent, attr, \ pool_name, pool_lv, name, size, usage_percent, attr, \
origin = line.split(';', 6) origin = line.split(';', 6)
if '' in [pool_name, pool_lv, name, size, usage_percent]: if '' in [pool_name, name, size, usage_percent]:
continue continue
name = pool_name + "/" + name name = pool_name + "/" + name
size = int(size[:-1]) # Remove 'B' suffix size = int(size[:-1]) # Remove 'B' suffix

View File

@ -369,6 +369,21 @@ class TC_03_FilePool(qubes.tests.QubesTestCase):
shutil.rmtree(pool_dir, ignore_errors=True) shutil.rmtree(pool_dir, ignore_errors=True)
def test_003_size(self):
pool = self.app.get_pool(self.POOL_NAME)
with self.assertNotRaises(NotImplementedError):
size = pool.size
statvfs = os.statvfs(self.POOL_DIR)
self.assertEqual(size, statvfs.f_blocks * statvfs.f_frsize)
def test_004_usage(self):
pool = self.app.get_pool(self.POOL_NAME)
with self.assertNotRaises(NotImplementedError):
usage = pool.usage
statvfs = os.statvfs(self.POOL_DIR)
self.assertEqual(usage,
statvfs.f_frsize * (statvfs.f_blocks - statvfs.f_bfree))
def test_011_appvm_file_images(self): def test_011_appvm_file_images(self):
""" Check if all the needed image files are created for an AppVm""" """ Check if all the needed image files are created for an AppVm"""

View File

@ -26,6 +26,7 @@
''' '''
import os import os
import subprocess
import unittest import unittest
import qubes.tests import qubes.tests
@ -158,6 +159,26 @@ class TC_00_ThinPool(ThinPoolBase):
self.assertTrue(os.path.exists(path)) self.assertTrue(os.path.exists(path))
volume.remove() volume.remove()
def test_004_size(self):
with self.assertNotRaises(NotImplementedError):
size = self.pool.size
pool_size = subprocess.check_output(['sudo', 'lvs', '--noheadings',
'-o', 'lv_size',
'--units', 'b', self.pool.volume_group + '/' + self.pool.thin_pool])
self.assertEqual(size, int(pool_size.strip()[:-1]))
def test_005_usage(self):
with self.assertNotRaises(NotImplementedError):
usage = self.pool.usage
pool_info = subprocess.check_output(['sudo', 'lvs', '--noheadings',
'-o', 'lv_size,data_percent',
'--units', 'b', self.pool.volume_group + '/' + self.pool.thin_pool])
pool_size, pool_usage = pool_info.strip().split()
pool_size = int(pool_size[:-1])
pool_usage = float(pool_usage)
self.assertEqual(usage, int(pool_size * pool_usage / 100))
@skipUnlessLvmPoolExists @skipUnlessLvmPoolExists
class TC_01_ThinPool(ThinPoolBase, qubes.tests.SystemTestCase): class TC_01_ThinPool(ThinPoolBase, qubes.tests.SystemTestCase):
''' Sanity tests for :py:class:`qubes.storage.lvm.ThinPool` ''' ''' Sanity tests for :py:class:`qubes.storage.lvm.ThinPool` '''