Merge branch 'storage-properties'
* storage-properties: storage: use None for size/usage properties if unknown tests: call search_pool_containing_dir with various dirs and pools storage: make DirectoryThinPool helper less verbose, add sudo api/admin: add 'included_in' to admin.pool.Info call storage: add Pool.included_in() method for checking nested pools storage: move and generalize RootThinPool helper class storage/kernels: refuse changes to 'rw' and 'revisions_to_keep' api/admin: implement admin.vm.volume.Set.rw method api/admin: include 'revisions_to_keep' and 'is_outdated' in volume info
This commit is contained in:
commit
e5413a3036
2
Makefile
2
Makefile
@ -30,6 +30,7 @@ ADMIN_API_METHODS_SIMPLE = \
|
||||
admin.pool.volume.Resize \
|
||||
admin.pool.volume.Revert \
|
||||
admin.pool.volume.Set.revisions_to_keep \
|
||||
admin.pool.volume.Set.rw \
|
||||
admin.pool.volume.Snapshot \
|
||||
admin.property.Get \
|
||||
admin.property.GetDefault \
|
||||
@ -100,6 +101,7 @@ ADMIN_API_METHODS_SIMPLE = \
|
||||
admin.vm.volume.Resize \
|
||||
admin.vm.volume.Revert \
|
||||
admin.vm.volume.Set.revisions_to_keep \
|
||||
admin.vm.volume.Set.rw \
|
||||
admin.vm.Stats \
|
||||
$(null)
|
||||
|
||||
|
@ -336,9 +336,16 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
# properties defined in API
|
||||
volume_properties = [
|
||||
'pool', 'vid', 'size', 'usage', 'rw', 'source',
|
||||
'save_on_stop', 'snap_on_start']
|
||||
return ''.join('{}={}\n'.format(key, getattr(volume, key)) for key in
|
||||
volume_properties)
|
||||
'save_on_stop', 'snap_on_start', 'revisions_to_keep', 'is_outdated']
|
||||
|
||||
def _serialize(value):
|
||||
if callable(value):
|
||||
value = value()
|
||||
if value is None:
|
||||
value = ''
|
||||
return str(value)
|
||||
return ''.join('{}={}\n'.format(key, _serialize(getattr(volume, key)))
|
||||
for key in volume_properties)
|
||||
|
||||
@qubes.api.method('admin.vm.volume.ListSnapshots', no_payload=True,
|
||||
scope='local', read=True)
|
||||
@ -496,6 +503,26 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
self.dest.volumes[self.arg].revisions_to_keep = newvalue
|
||||
self.app.save()
|
||||
|
||||
@qubes.api.method('admin.vm.volume.Set.rw',
|
||||
scope='local', write=True)
|
||||
@asyncio.coroutine
|
||||
def vm_volume_set_rw(self, untrusted_payload):
|
||||
assert self.arg in self.dest.volumes.keys()
|
||||
try:
|
||||
newvalue = qubes.property.bool(None, None,
|
||||
untrusted_payload.decode('ascii'))
|
||||
except (UnicodeDecodeError, ValueError):
|
||||
raise qubes.api.ProtocolError('Invalid value')
|
||||
del untrusted_payload
|
||||
|
||||
self.fire_event_for_permission(newvalue=newvalue)
|
||||
|
||||
if not self.dest.is_halted():
|
||||
raise qubes.exc.QubesVMNotHaltedError(self.dest)
|
||||
|
||||
self.dest.volumes[self.arg].rw = newvalue
|
||||
self.app.save()
|
||||
|
||||
@qubes.api.method('admin.vm.tag.List', no_payload=True,
|
||||
scope='local', read=True)
|
||||
@asyncio.coroutine
|
||||
@ -579,19 +606,25 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
|
||||
self.fire_event_for_permission(pool=pool)
|
||||
|
||||
size_info = ''
|
||||
other_info = ''
|
||||
pool_size = pool.size
|
||||
if pool_size is not None:
|
||||
other_info += 'size={}\n'.format(pool_size)
|
||||
|
||||
pool_usage = pool.usage
|
||||
if pool_usage is not None:
|
||||
other_info += 'usage={}\n'.format(pool_usage)
|
||||
|
||||
try:
|
||||
size_info += 'size={}\n'.format(pool.size)
|
||||
except NotImplementedError:
|
||||
pass
|
||||
try:
|
||||
size_info += 'usage={}\n'.format(pool.usage)
|
||||
included_in = pool.included_in(self.app)
|
||||
if included_in:
|
||||
other_info += 'included_in={}\n'.format(str(included_in))
|
||||
except NotImplementedError:
|
||||
pass
|
||||
|
||||
return ''.join('{}={}\n'.format(prop, val)
|
||||
for prop, val in sorted(pool.config.items())) + \
|
||||
size_info
|
||||
other_info
|
||||
|
||||
@qubes.api.method('admin.pool.Add',
|
||||
scope='global', write=True)
|
||||
|
62
qubes/app.py
62
qubes/app.py
@ -24,21 +24,19 @@ import collections
|
||||
import errno
|
||||
import functools
|
||||
import grp
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import traceback
|
||||
import uuid
|
||||
|
||||
import itertools
|
||||
import lxml.etree
|
||||
|
||||
import jinja2
|
||||
import libvirt
|
||||
import lxml.etree
|
||||
|
||||
try:
|
||||
import xen.lowlevel.xs # pylint: disable=wrong-import-order
|
||||
@ -548,54 +546,6 @@ class VMCollection(object):
|
||||
'https://xkcd.com/221/',
|
||||
'http://dilbert.com/strip/2001-10-25')[random.randint(0, 1)])
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class RootThinPool:
|
||||
'''The thin pool containing the rootfs device'''
|
||||
_inited = False
|
||||
_volume_group = None
|
||||
_thin_pool = None
|
||||
|
||||
@classmethod
|
||||
def _init(cls):
|
||||
'''Find out the thin pool containing the root device'''
|
||||
if not cls._inited:
|
||||
cls._inited = True
|
||||
|
||||
try:
|
||||
rootfs = os.stat('/')
|
||||
root_major = (rootfs.st_dev & 0xff00) >> 8
|
||||
root_minor = rootfs.st_dev & 0xff
|
||||
|
||||
root_table = subprocess.check_output(["dmsetup",
|
||||
"-j", str(root_major), "-m", str(root_minor),
|
||||
"table"])
|
||||
|
||||
_start, _sectors, target_type, target_args = \
|
||||
root_table.decode().split(" ", 3)
|
||||
if target_type == "thin":
|
||||
thin_pool_devnum, _thin_pool_id = target_args.split(" ")
|
||||
with open("/sys/dev/block/{}/dm/name"
|
||||
.format(thin_pool_devnum), "r") as thin_pool_tpool_f:
|
||||
thin_pool_tpool = thin_pool_tpool_f.read().rstrip('\n')
|
||||
if thin_pool_tpool.endswith("-tpool"):
|
||||
volume_group, thin_pool, _tpool = \
|
||||
thin_pool_tpool.rsplit("-", 2)
|
||||
cls._volume_group = volume_group
|
||||
cls._thin_pool = thin_pool
|
||||
except: # pylint: disable=bare-except
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def volume_group(cls):
|
||||
'''Volume group of the thin pool containing the rootfs device'''
|
||||
cls._init()
|
||||
return cls._volume_group
|
||||
|
||||
@classmethod
|
||||
def thin_pool(cls):
|
||||
'''Thin pool name containing the rootfs device'''
|
||||
cls._init()
|
||||
return cls._thin_pool
|
||||
|
||||
def _default_pool(app):
|
||||
''' Default storage pool.
|
||||
@ -616,8 +566,8 @@ def _default_pool(app):
|
||||
if pool.config['thin_pool'] == thin_pool:
|
||||
return pool
|
||||
# no DEFAULT_LVM_POOL, or pool not defined
|
||||
root_volume_group = RootThinPool.volume_group()
|
||||
root_thin_pool = RootThinPool.thin_pool()
|
||||
root_volume_group, root_thin_pool = \
|
||||
qubes.storage.DirectoryThinPool.thin_pool('/')
|
||||
if root_thin_pool:
|
||||
for pool in app.pools.values():
|
||||
if pool.config.get('driver', None) != 'lvm_thin':
|
||||
@ -1114,8 +1064,8 @@ class Qubes(qubes.PropertyHolder):
|
||||
}
|
||||
assert max(self.labels.keys()) == qubes.config.max_default_label
|
||||
|
||||
root_volume_group = RootThinPool.volume_group()
|
||||
root_thin_pool = RootThinPool.thin_pool()
|
||||
root_volume_group, root_thin_pool = \
|
||||
qubes.storage.DirectoryThinPool.thin_pool('/')
|
||||
|
||||
if root_thin_pool:
|
||||
self.add_pool(
|
||||
|
@ -815,15 +815,26 @@ class Pool(object):
|
||||
'''
|
||||
raise self._not_implemented("get_volume")
|
||||
|
||||
def included_in(self, app):
|
||||
''' Check if this pool is physically included in another one
|
||||
|
||||
This works on best-effort basis, because one pool driver may not know
|
||||
all the other drivers.
|
||||
|
||||
:param app: Qubes() object to lookup other pools in
|
||||
:returns pool or None
|
||||
'''
|
||||
pass
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
''' Storage pool size in bytes '''
|
||||
raise self._not_implemented("size")
|
||||
''' Storage pool size in bytes, or None if unknown '''
|
||||
return None
|
||||
|
||||
@property
|
||||
def usage(self):
|
||||
''' Space used in the pool, in bytes '''
|
||||
raise self._not_implemented("usage")
|
||||
''' Space used in the pool in bytes, or None if unknown '''
|
||||
return None
|
||||
|
||||
def _not_implemented(self, method_name):
|
||||
''' Helper for emitting helpful `NotImplementedError` exceptions '''
|
||||
@ -865,6 +876,27 @@ def isodate(seconds=time.time()):
|
||||
''' Helper method which returns an iso date '''
|
||||
return datetime.utcfromtimestamp(seconds).isoformat("T")
|
||||
|
||||
def search_pool_containing_dir(pools, dir_path):
|
||||
''' Helper function looking for a pool containing given directory.
|
||||
|
||||
This is useful for implementing Pool.included_in method
|
||||
'''
|
||||
|
||||
# prefer filesystem pools
|
||||
for pool in pools:
|
||||
if hasattr(pool, 'dir_path'):
|
||||
if dir_path.startswith(pool.dir_path):
|
||||
return pool
|
||||
|
||||
# then look for lvm
|
||||
for pool in pools:
|
||||
if hasattr(pool, 'thin_pool') and hasattr(pool, 'volume_group'):
|
||||
if (pool.volume_group, pool.thin_pool) == \
|
||||
DirectoryThinPool.thin_pool(dir_path):
|
||||
return pool
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class VmCreationManager(object):
|
||||
''' A `ContextManager` which cleans up if volume creation fails.
|
||||
@ -883,3 +915,46 @@ class VmCreationManager(object):
|
||||
except Exception: # pylint: disable=broad-except
|
||||
pass
|
||||
os.rmdir(self.vm.dir_path)
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class DirectoryThinPool:
|
||||
'''The thin pool containing the device of given filesystem'''
|
||||
_thin_pool = {}
|
||||
|
||||
@classmethod
|
||||
def _init(cls, dir_path):
|
||||
'''Find out the thin pool containing given filesystem'''
|
||||
if dir_path not in cls._thin_pool:
|
||||
cls._thin_pool[dir_path] = None, None
|
||||
|
||||
try:
|
||||
fs_stat = os.stat(dir_path)
|
||||
fs_major = (fs_stat.st_dev & 0xff00) >> 8
|
||||
fs_minor = fs_stat.st_dev & 0xff
|
||||
|
||||
sudo = []
|
||||
if os.getuid():
|
||||
sudo = ['sudo']
|
||||
root_table = subprocess.check_output(sudo + ["dmsetup",
|
||||
"-j", str(fs_major), "-m", str(fs_minor),
|
||||
"table"], stderr=subprocess.DEVNULL)
|
||||
|
||||
_start, _sectors, target_type, target_args = \
|
||||
root_table.decode().split(" ", 3)
|
||||
if target_type == "thin":
|
||||
thin_pool_devnum, _thin_pool_id = target_args.split(" ")
|
||||
with open("/sys/dev/block/{}/dm/name"
|
||||
.format(thin_pool_devnum), "r") as thin_pool_tpool_f:
|
||||
thin_pool_tpool = thin_pool_tpool_f.read().rstrip('\n')
|
||||
if thin_pool_tpool.endswith("-tpool"):
|
||||
volume_group, thin_pool, _tpool = \
|
||||
thin_pool_tpool.rsplit("-", 2)
|
||||
cls._thin_pool[dir_path] = volume_group, thin_pool
|
||||
except: # pylint: disable=bare-except
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def thin_pool(cls, dir_path):
|
||||
'''Thin tuple (volume group, pool name) containing given filesystem'''
|
||||
cls._init(dir_path)
|
||||
return cls._thin_pool[dir_path]
|
||||
|
@ -163,6 +163,12 @@ class FilePool(qubes.storage.Pool):
|
||||
statvfs = os.statvfs(self.dir_path)
|
||||
return statvfs.f_frsize * (statvfs.f_blocks - statvfs.f_bfree)
|
||||
|
||||
def included_in(self, app):
|
||||
''' Check if there is pool containing this one - either as a
|
||||
filesystem or its LVM volume'''
|
||||
return qubes.storage.search_pool_containing_dir(
|
||||
[pool for pool in app.pools.values() if pool is not self],
|
||||
self.dir_path)
|
||||
|
||||
class FileVolume(qubes.storage.Volume):
|
||||
''' Parent class for the xen volumes implementation which expects a
|
||||
|
@ -23,18 +23,21 @@
|
||||
|
||||
import os
|
||||
|
||||
import qubes.exc
|
||||
import qubes.storage
|
||||
from qubes.storage import Pool, StoragePoolException, Volume
|
||||
|
||||
|
||||
class LinuxModules(Volume):
|
||||
''' A volume representing a ro linux kernel '''
|
||||
rw = False
|
||||
|
||||
def __init__(self, target_dir, kernel_version, **kwargs):
|
||||
kwargs['vid'] = ''
|
||||
super(LinuxModules, self).__init__(**kwargs)
|
||||
self._kernel_version = kernel_version
|
||||
self.target_dir = target_dir
|
||||
assert self.revisions_to_keep == 0
|
||||
assert self.rw is False
|
||||
|
||||
@property
|
||||
def vid(self):
|
||||
@ -104,6 +107,28 @@ class LinuxModules(Volume):
|
||||
def is_outdated(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def revisions_to_keep(self):
|
||||
return 0
|
||||
|
||||
@revisions_to_keep.setter
|
||||
def revisions_to_keep(self, value):
|
||||
# pylint: disable=no-self-use
|
||||
if value:
|
||||
raise qubes.exc.QubesValueError(
|
||||
'LinuxModules supports only revisions_to_keep=0')
|
||||
|
||||
@property
|
||||
def rw(self):
|
||||
return False
|
||||
|
||||
@rw.setter
|
||||
def rw(self, value):
|
||||
# pylint: disable=no-self-use
|
||||
if value:
|
||||
raise qubes.exc.QubesValueError(
|
||||
'LinuxModules supports only read-only volumes')
|
||||
|
||||
def start(self):
|
||||
return self
|
||||
|
||||
@ -131,7 +156,7 @@ class LinuxKernel(Pool):
|
||||
|
||||
def __init__(self, name=None, dir_path=None):
|
||||
assert dir_path, 'Missing dir_path'
|
||||
super(LinuxKernel, self).__init__(name=name)
|
||||
super(LinuxKernel, self).__init__(name=name, revisions_to_keep=0)
|
||||
self.dir_path = dir_path
|
||||
|
||||
def init_volume(self, vm, volume_config):
|
||||
@ -167,6 +192,23 @@ class LinuxKernel(Pool):
|
||||
def setup(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def revisions_to_keep(self):
|
||||
return 0
|
||||
|
||||
@revisions_to_keep.setter
|
||||
def revisions_to_keep(self, value):
|
||||
# pylint: disable=no-self-use
|
||||
if value:
|
||||
raise qubes.exc.QubesValueError(
|
||||
'LinuxModules supports only revisions_to_keep=0')
|
||||
|
||||
def included_in(self, app):
|
||||
''' Check if there is pool containing /var/lib/qubes/vm-kernels '''
|
||||
return qubes.storage.search_pool_containing_dir(
|
||||
[pool for pool in app.pools.values() if pool is not self],
|
||||
self.dir_path)
|
||||
|
||||
@property
|
||||
def volumes(self):
|
||||
''' Return all known kernel volumes '''
|
||||
|
@ -108,6 +108,12 @@ class ReflinkPool(qubes.storage.Pool):
|
||||
statvfs = os.statvfs(self.dir_path)
|
||||
return statvfs.f_frsize * (statvfs.f_blocks - statvfs.f_bfree)
|
||||
|
||||
def included_in(self, app):
|
||||
''' Check if there is pool containing this one - either as a
|
||||
filesystem or its LVM volume'''
|
||||
return qubes.storage.search_pool_containing_dir(
|
||||
[pool for pool in app.pools.values() if pool is not self],
|
||||
self.dir_path)
|
||||
|
||||
class ReflinkVolume(qubes.storage.Volume):
|
||||
def create(self):
|
||||
|
@ -40,7 +40,7 @@ import qubes.storage
|
||||
# properties defined in API
|
||||
volume_properties = [
|
||||
'pool', 'vid', 'size', 'usage', 'rw', 'source',
|
||||
'save_on_stop', 'snap_on_start']
|
||||
'save_on_stop', 'snap_on_start', 'revisions_to_keep', 'is_outdated']
|
||||
|
||||
|
||||
class AdminAPITestCase(qubes.tests.QubesTestCase):
|
||||
@ -557,6 +557,7 @@ class TC_00_VMs(AdminAPITestCase):
|
||||
usage=102400,
|
||||
size=204800)
|
||||
}
|
||||
self.app.pools['pool1'].included_in.return_value = None
|
||||
value = self.call_mgmt_func(b'admin.pool.Info', b'dom0', b'pool1')
|
||||
|
||||
self.assertEqual(value,
|
||||
@ -566,18 +567,34 @@ class TC_00_VMs(AdminAPITestCase):
|
||||
def test_151_pool_info_unsupported_size(self):
|
||||
self.app.pools = {
|
||||
'pool1': unittest.mock.Mock(config={
|
||||
'param1': 'value1', 'param2': 'value2'})
|
||||
'param1': 'value1', 'param2': 'value2'},
|
||||
size=None, usage=None),
|
||||
}
|
||||
type(self.app.pools['pool1']).size = unittest.mock.PropertyMock(
|
||||
side_effect=NotImplementedError)
|
||||
type(self.app.pools['pool1']).usage = unittest.mock.PropertyMock(
|
||||
side_effect=NotImplementedError)
|
||||
self.app.pools['pool1'].included_in.return_value = None
|
||||
value = self.call_mgmt_func(b'admin.pool.Info', b'dom0', b'pool1')
|
||||
|
||||
self.assertEqual(value,
|
||||
'param1=value1\nparam2=value2\n')
|
||||
self.assertFalse(self.app.save.called)
|
||||
|
||||
def test_152_pool_info_included_in(self):
|
||||
self.app.pools = {
|
||||
'pool1': unittest.mock.MagicMock(config={
|
||||
'param1': 'value1',
|
||||
'param2': 'value2'},
|
||||
usage=102400,
|
||||
size=204800)
|
||||
}
|
||||
self.app.pools['pool1'].included_in.return_value = \
|
||||
self.app.pools['pool1']
|
||||
self.app.pools['pool1'].__str__.return_value = 'pool1'
|
||||
value = self.call_mgmt_func(b'admin.pool.Info', b'dom0', b'pool1')
|
||||
|
||||
self.assertEqual(value,
|
||||
'param1=value1\nparam2=value2\nsize=204800\nusage=102400'
|
||||
'\nincluded_in=pool1\n')
|
||||
self.assertFalse(self.app.save.called)
|
||||
|
||||
@unittest.mock.patch('qubes.storage.pool_drivers')
|
||||
@unittest.mock.patch('qubes.storage.driver_parameters')
|
||||
def test_160_pool_add(self, mock_parameters, mock_drivers):
|
||||
@ -2373,6 +2390,34 @@ class TC_00_VMs(AdminAPITestCase):
|
||||
self.call_mgmt_func(b'admin.vm.volume.Set.revisions_to_keep',
|
||||
b'test-vm1', b'private', b'abc')
|
||||
|
||||
def test_680_vm_volume_set_rw(self):
|
||||
self.vm.volumes = unittest.mock.MagicMock()
|
||||
volumes_conf = {
|
||||
'keys.return_value': ['root', 'private', 'volatile', 'kernel'],
|
||||
}
|
||||
self.vm.volumes.configure_mock(**volumes_conf)
|
||||
self.vm.storage = unittest.mock.Mock()
|
||||
value = self.call_mgmt_func(b'admin.vm.volume.Set.rw',
|
||||
b'test-vm1', b'private', b'True')
|
||||
self.assertIsNone(value)
|
||||
self.assertEqual(self.vm.volumes.mock_calls,
|
||||
[unittest.mock.call.keys(),
|
||||
('__getitem__', ('private',), {})])
|
||||
self.assertEqual(self.vm.volumes['private'].rw, True)
|
||||
self.app.save.assert_called_once_with()
|
||||
|
||||
def test_681_vm_volume_set_rw_invalid(self):
|
||||
self.vm.volumes = unittest.mock.MagicMock()
|
||||
volumes_conf = {
|
||||
'keys.return_value': ['root', 'private', 'volatile', 'kernel'],
|
||||
}
|
||||
self.vm.volumes.configure_mock(**volumes_conf)
|
||||
self.vm.storage = unittest.mock.Mock()
|
||||
with self.assertRaises(AssertionError):
|
||||
self.call_mgmt_func(b'admin.vm.volume.Set.revisions_to_keep',
|
||||
b'test-vm1', b'private', b'abc')
|
||||
self.assertFalse(self.app.save.called)
|
||||
|
||||
def test_990_vm_unexpected_payload(self):
|
||||
methods_with_no_payload = [
|
||||
b'admin.vm.List',
|
||||
|
@ -27,9 +27,11 @@
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import qubes.tests
|
||||
import qubes.storage
|
||||
from qubes.storage.lvm import ThinPool, ThinVolume
|
||||
|
||||
if 'DEFAULT_LVM_POOL' in os.environ.keys():
|
||||
@ -267,3 +269,76 @@ class TC_01_ThinPool(ThinPoolBase, qubes.tests.SystemTestCase):
|
||||
self.assertEqual(volume.path, expected)
|
||||
with self.assertNotRaises(qubes.exc.QubesException):
|
||||
vm.start()
|
||||
|
||||
@skipUnlessLvmPoolExists
|
||||
class TC_02_StorageHelpers(ThinPoolBase):
|
||||
def setUp(self):
|
||||
xml_path = '/tmp/qubes-test.xml'
|
||||
self.app = qubes.Qubes.create_empty_store(store=xml_path,
|
||||
clockvm=None,
|
||||
updatevm=None,
|
||||
offline_mode=True,
|
||||
)
|
||||
os.environ['QUBES_XML_PATH'] = xml_path
|
||||
super(TC_02_StorageHelpers, self).setUp()
|
||||
# reset cache
|
||||
qubes.storage.DirectoryThinPool._thin_pool = {}
|
||||
|
||||
self.thin_dir = tempfile.TemporaryDirectory()
|
||||
subprocess.check_call(
|
||||
['sudo', 'lvcreate', '-q', '-V', '32M',
|
||||
'-T', DEFAULT_LVM_POOL, '-n',
|
||||
'test-file-pool'], stdout=subprocess.DEVNULL)
|
||||
self.thin_dev = '/dev/{}/test-file-pool'.format(
|
||||
DEFAULT_LVM_POOL.split('/')[0])
|
||||
subprocess.check_call(
|
||||
['sudo', 'mkfs.ext4', '-q', self.thin_dev])
|
||||
subprocess.check_call(['sudo', 'mount', self.thin_dev,
|
||||
self.thin_dir.name])
|
||||
subprocess.check_call(['sudo', 'chmod', '777',
|
||||
self.thin_dir.name])
|
||||
|
||||
def tearDown(self):
|
||||
subprocess.check_call(['sudo', 'umount', self.thin_dir.name])
|
||||
subprocess.check_call(
|
||||
['sudo', 'lvremove', '-q', '-f', self.thin_dev],
|
||||
stdout = subprocess.DEVNULL)
|
||||
self.thin_dir.cleanup()
|
||||
super(TC_02_StorageHelpers, self).tearDown()
|
||||
os.unlink(self.app.store)
|
||||
del self.app
|
||||
for attr in dir(self):
|
||||
if isinstance(getattr(self, attr), qubes.vm.BaseVM):
|
||||
delattr(self, attr)
|
||||
|
||||
def test_000_search_thin_pool(self):
|
||||
pool = qubes.storage.search_pool_containing_dir(
|
||||
self.app.pools.values(), self.thin_dir.name)
|
||||
self.assertEqual(pool, self.pool)
|
||||
|
||||
def test_001_search_none(self):
|
||||
pool = qubes.storage.search_pool_containing_dir(
|
||||
self.app.pools.values(), '/tmp')
|
||||
self.assertIsNone(pool)
|
||||
|
||||
def test_002_search_subdir(self):
|
||||
subdir = os.path.join(self.thin_dir.name, 'some-dir')
|
||||
os.mkdir(subdir)
|
||||
pool = qubes.storage.search_pool_containing_dir(
|
||||
self.app.pools.values(), subdir)
|
||||
self.assertEqual(pool, self.pool)
|
||||
|
||||
def test_003_search_file_pool(self):
|
||||
subdir = os.path.join(self.thin_dir.name, 'some-dir')
|
||||
file_pool_config = {
|
||||
'name': 'test-file-pool',
|
||||
'driver': 'file',
|
||||
'dir_path': subdir
|
||||
}
|
||||
pool2 = self.app.add_pool(**file_pool_config)
|
||||
pool = qubes.storage.search_pool_containing_dir(
|
||||
self.app.pools.values(), subdir)
|
||||
self.assertEqual(pool, pool2)
|
||||
pool = qubes.storage.search_pool_containing_dir(
|
||||
self.app.pools.values(), self.thin_dir.name)
|
||||
self.assertEqual(pool, self.pool)
|
||||
|
Loading…
Reference in New Issue
Block a user