Merge remote-tracking branch 'qubesos/pr/166'

* qubesos/pr/166:
  create "lvm" pool using rootfs thin pool instead of hardcoding qubes_dom0-pool00
  change default pool code to be fast
  cache PropertyHolder.property_list and use O(1) property name lookups
  remove unused netid code
  cache isinstance(default, collections.Callable)
  don't access netvm if it's None in visible_gateway/netmask
This commit is contained in:
Marek Marczykowski-Górecki 2017-12-06 00:41:37 +01:00
commit 7b81d7affa
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
7 changed files with 121 additions and 59 deletions

View File

@ -200,6 +200,10 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name
lambda self, prop, value: str(value))
self.type = type
self._default = default
self._default_function = None
if isinstance(default, collections.Callable):
self._default_function = default
self._write_once = write_once
self.order = order
self.load_stage = load_stage
@ -227,8 +231,8 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name
if self._default is self._NO_DEFAULT:
raise AttributeError(
'property {!r} have no default'.format(self.__name__))
elif isinstance(self._default, collections.Callable):
return self._default(instance)
elif self._default_function:
return self._default_function(instance)
else:
return self._default
@ -492,7 +496,7 @@ class PropertyHolder(qubes.events.Emitter):
propvalues = {}
all_names = set(prop.__name__ for prop in self.property_list())
all_names = self.property_dict()
for key in list(kwargs):
if not key in all_names:
continue
@ -505,8 +509,6 @@ class PropertyHolder(qubes.events.Emitter):
if self.xml is not None:
# check if properties are appropriate
all_names = set(prop.__name__ for prop in self.property_list())
for node in self.xml.xpath('./properties/property'):
name = node.get('name')
if name not in all_names:
@ -514,6 +516,39 @@ class PropertyHolder(qubes.events.Emitter):
'property {!r} not applicable to {!r}'.format(
name, self.__class__.__name__))
# pylint: disable=too-many-nested-blocks
@classmethod
def property_dict(cls, load_stage=None):
'''List all properties attached to this VM's class
:param load_stage: Filter by load stage
:type load_stage: :py:func:`int` or :py:obj:`None`
'''
# use cls.__dict__ since we must not look at parent classes
if "_property_dict" not in cls.__dict__:
cls._property_dict = {}
memo = cls._property_dict
if load_stage not in memo:
props = dict()
if load_stage is None:
for class_ in cls.__mro__:
for name in class_.__dict__:
# don't overwrite props with those from base classes
if name not in props:
prop = class_.__dict__[name]
if isinstance(prop, property):
assert name == prop.__name__
props[name] = prop
else:
for prop in cls.property_dict().values():
if prop.load_stage == load_stage:
props[prop.__name__] = prop
memo[load_stage] = props
return memo[load_stage]
@classmethod
def property_list(cls, load_stage=None):
'''List all properties attached to this VM's class
@ -522,14 +557,15 @@ class PropertyHolder(qubes.events.Emitter):
:type load_stage: :py:func:`int` or :py:obj:`None`
'''
props = set()
for class_ in cls.__mro__:
props.update(prop for prop in class_.__dict__.values()
if isinstance(prop, property))
if load_stage is not None:
props = set(prop for prop in props
if prop.load_stage == load_stage)
return sorted(props)
# use cls.__dict__ since we must not look at parent classes
if "_property_list" not in cls.__dict__:
cls._property_list = {}
memo = cls._property_list
if load_stage not in memo:
memo[load_stage] = sorted(cls.property_dict(load_stage).values())
return memo[load_stage]
def _property_init(self, prop, value):
'''Initialise property to a given value, without side effects.
@ -584,9 +620,9 @@ class PropertyHolder(qubes.events.Emitter):
if isinstance(prop, qubes.property):
return prop
for p in cls.property_list():
if p.__name__ == prop:
return p
props = cls.property_dict()
if prop in props:
return props[prop]
raise AttributeError('No property {!r} found in {!r}'.format(
prop, cls))

View File

@ -538,14 +538,6 @@ class VMCollection(object):
raise LookupError("Cannot find unused qid!")
def get_new_unused_netid(self):
used_ids = set([vm.netid for vm in self]) # if vm.is_netvm()])
for i in range(1, qubes.config.max_netid):
if i not in used_ids:
return i
raise LookupError("Cannot find unused netid!")
def get_new_unused_dispid(self):
for _ in range(int(qubes.config.max_dispid ** 0.5)):
dispid = random.SystemRandom().randrange(qubes.config.max_dispid)
@ -555,6 +547,55 @@ 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.
@ -566,20 +607,16 @@ def _default_pool(app):
if 'default' in app.pools:
return app.pools['default']
else:
rootfs = os.stat('/')
root_major = (rootfs.st_dev & 0xff00) >> 8
root_minor = rootfs.st_dev & 0xff
for pool in app.pools.values():
if pool.config.get('driver', None) != 'lvm_thin':
continue
thin_pool = pool.config['thin_pool']
thin_volumes = subprocess.check_output(
['lvs', '--select', 'pool_lv=' + thin_pool,
'-o', 'lv_kernel_major,lv_kernel_minor', '--noheadings'])
thin_volumes = thin_volumes.decode()
if any([str(root_major), str(root_minor)] == thin_vol.split()
for thin_vol in thin_volumes.splitlines()):
return pool
root_volume_group = RootThinPool.volume_group()
root_thin_pool = RootThinPool.thin_pool()
if root_thin_pool:
for pool in app.pools.values():
if pool.config.get('driver', None) != 'lvm_thin':
continue
if (pool.config['volume_group'] == root_volume_group and
pool.config['thin_pool'] == root_thin_pool):
return pool
# not a thin volume? look for file pools
for pool in app.pools.values():
if pool.config.get('driver', None) != 'file':
@ -1005,10 +1042,13 @@ class Qubes(qubes.PropertyHolder):
}
assert max(self.labels.keys()) == qubes.config.max_default_label
# check if the default LVM Thin pool qubes_dom0/pool00 exists
if os.path.exists('/dev/mapper/qubes_dom0-pool00-tpool'):
self.add_pool(volume_group='qubes_dom0', thin_pool='pool00',
name='lvm', driver='lvm_thin')
root_volume_group = RootThinPool.volume_group()
root_thin_pool = RootThinPool.thin_pool()
if root_thin_pool:
self.add_pool(
volume_group=root_volume_group, thin_pool=root_thin_pool,
name='lvm', driver='lvm_thin')
# pool based on /var/lib/qubes will be created here:
for name, config in qubes.config.defaults['pool_configs'].items():
self.pools[name] = self._get_pool(**config)

View File

@ -100,7 +100,6 @@ defaults = {
}
max_qid = 254
max_netid = 254
max_dispid = 10000
#: built-in standard labels, if creating new one, allocate them above this
# number, at least until label index is removed from API

View File

@ -257,12 +257,6 @@ class TC_30_VMCollection(qubes.tests.QubesTestCase):
self.vms.get_new_unused_qid()
def test_101_get_new_unused_netid(self):
self.vms.add(self.testvm1)
self.vms.add(self.testvm2)
self.vms.get_new_unused_netid()
# def test_200_get_vms_based_on(self):
# pass

View File

@ -323,7 +323,6 @@ class TC_20_PropertyHolder(qubes.tests.QubesTestCase):
class TestVM(qubes.vm.BaseVM):
qid = qubes.property('qid', type=int)
name = qubes.property('name')
netid = qid
uuid = uuid.uuid5(uuid.NAMESPACE_DNS, 'testvm')
def __lt__(self, other):
@ -452,12 +451,6 @@ class TC_30_VMCollection(qubes.tests.QubesTestCase):
self.vms.get_new_unused_qid()
def test_101_get_new_unused_netid(self):
self.vms.add(self.testvm1)
self.vms.add(self.testvm2)
self.vms.get_new_unused_netid()
# def test_200_get_vms_based_on(self):
# pass

View File

@ -189,7 +189,7 @@ class AdminVM(qubes.vm.BaseVM):
# def __init__(self, **kwargs):
# super(QubesAdminVm, self).__init__(qid=0, name="dom0", netid=0,
# super(QubesAdminVm, self).__init__(qid=0, name="dom0",
# dir_path=None,
# private_img = None,
# template = None,

View File

@ -123,13 +123,13 @@ class NetVMMixin(qubes.events.Emitter):
def visible_gateway(self):
'''Default gateway of this domain as seen by the domain.'''
return self.features.check_with_template('net.fake-gateway', None) or \
self.netvm.gateway
(self.netvm.gateway if self.netvm else None)
@qubes.stateless_property
def visible_netmask(self):
'''Netmask as seen by the domain.'''
return self.features.check_with_template('net.fake-netmask', None) or \
self.netvm.netmask
(self.netvm.netmask if self.netvm else None)
#
# used in netvms (provides_network=True)