Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simple server optimizations #166

Merged
merged 6 commits into from
Dec 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 52 additions & 16 deletions qubes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ def __init__(self, name, setter=None, saver=None, type=None,
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
Expand Down Expand Up @@ -227,8 +231,8 @@ def get_default(self, instance):
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

Expand Down Expand Up @@ -492,7 +496,7 @@ def __init__(self, xml, **kwargs):

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
Expand All @@ -505,15 +509,46 @@ def __init__(self, xml, **kwargs):

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:
raise TypeError(
'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
Expand All @@ -522,14 +557,15 @@ def property_list(cls, load_stage=None):
: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.
Expand Down Expand Up @@ -584,9 +620,9 @@ def property_get_def(cls, prop):
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))
Expand Down
92 changes: 66 additions & 26 deletions qubes/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,14 +536,6 @@ def get_new_unused_qid(self):
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)
Expand All @@ -553,6 +545,55 @@ def get_new_unused_dispid(self):
'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.

Expand All @@ -564,20 +605,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':
Expand Down Expand Up @@ -1003,10 +1040,13 @@ def load_initial_values(self):
}
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)
Expand Down
1 change: 0 additions & 1 deletion qubes/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@
}

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
Expand Down
6 changes: 0 additions & 6 deletions qubes/tests/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,6 @@ def test_100_get_new_unused_qid(self):

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

Expand Down
7 changes: 0 additions & 7 deletions qubes/tests/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,6 @@ def test_010_property_require(self):
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):
Expand Down Expand Up @@ -452,12 +451,6 @@ def test_100_get_new_unused_qid(self):

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

Expand Down
2 changes: 1 addition & 1 deletion qubes/vm/adminvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def untrusted_qdb(self):


# 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,
Expand Down
4 changes: 2 additions & 2 deletions qubes/vm/mix/net.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,13 @@ def visible_ip(self):
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)
Expand Down